Salvataggio di un oggetto (persistenza dei dati)


233

Ho creato un oggetto come questo:

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

Vorrei salvare questo oggetto. Come lo posso fare?


1
Vedi l' esempio per le persone che vengono qui per un semplice esempio su come usare il sottaceto.
Martin Thoma,

@MartinThoma: Perché (apparentemente) preferisci quella risposta a quella accettata (della domanda collegata )?
martineau,

Al momento ho collegato, la risposta accettata non aveva protocol=pickle.HIGHEST_PROTOCOL. La mia risposta offre anche alternative al sottaceto.
Martin Thoma,

Risposte:


449

È possibile utilizzare il picklemodulo nella libreria standard. Ecco una sua elementare applicazione al tuo esempio:

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

Puoi anche definire la tua semplice utility come la seguente che apre un file e scrive un singolo oggetto su di esso:

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

Aggiornare

Dal momento che questa è una risposta così popolare, vorrei toccare alcuni argomenti di utilizzo leggermente avanzati.

cPickle(o _pickle) vspickle

È quasi sempre preferibile utilizzare effettivamente il cPicklemodulo piuttosto che pickleperché il primo è scritto in C ed è molto più veloce. Ci sono alcune sottili differenze tra loro, ma nella maggior parte delle situazioni sono equivalenti e la versione C fornirà prestazioni notevolmente superiori. Passare ad esso non potrebbe essere più semplice, basta cambiare l' importistruzione in questo:

import cPickle as pickle

In Python 3, è cPicklestato rinominato _pickle, ma farlo non è più necessario poiché il picklemodulo ora lo fa automaticamente: vedi Che differenza tra pickle e _pickle in python 3? .

Il riassunto è che potresti usare qualcosa di simile al seguente per assicurarti che il tuo codice userà sempre la versione C quando è disponibile in Python 2 e 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Formati del flusso di dati (protocolli)

picklepuò leggere e scrivere file in diversi formati specifici di Python, chiamati protocolli come descritto nella documentazione , "Protocollo versione 0" è ASCII e quindi "leggibile dall'uomo". Le versioni> 0 sono binarie e la più alta disponibile dipende dalla versione di Python utilizzata. L'impostazione predefinita dipende anche dalla versione di Python. In Python 2 l'impostazione predefinita era Versione protocollo 0, ma in Python 3.8.1 è la versione Protocollo 4. In Python 3.x al modulo è stato pickle.DEFAULT_PROTOCOLaggiunto un modulo , ma ciò non esiste in Python 2.

Fortunatamente c'è una scorciatoia per scrivere pickle.HIGHEST_PROTOCOLin ogni chiamata (supponendo che sia quello che vuoi, e di solito lo fai), basta usare il numero letterale -1- simile a fare riferimento all'ultimo elemento di una sequenza tramite un indice negativo. Quindi, invece di scrivere:

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

Puoi semplicemente scrivere:

pickle.dump(obj, output, -1)

In entrambi i casi, avresti specificato il protocollo una sola volta se avessi creato un Pickleroggetto da utilizzare in più operazioni di pickle:

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

Nota : se ti trovi in ​​un ambiente con versioni diverse di Python, probabilmente vorrai utilizzare esplicitamente (ad esempio un hardcode) un numero di protocollo specifico che tutti possono leggere (le versioni successive possono generalmente leggere i file prodotti da quelli precedenti) .

Oggetti multipli

Mentre un file di salamoia può contenere qualsiasi numero di oggetti in salamoia, come mostrato nei campioni di cui sopra, quando c'è un numero imprecisato di loro, è spesso più facile per memorizzare tutti in una sorta di contenitore variabile dimensioni, come una list, tupleo dicte scrivere tutti al file in una sola chiamata:

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

e ripristina l'elenco e tutto il resto in seguito con:

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

Il vantaggio principale è che non è necessario sapere quante istanze di oggetto vengono salvate per poterle ricaricare in un secondo momento (anche se è possibile farlo senza tali informazioni , richiede un codice leggermente specializzato). Vedi le risposte alla domanda correlata Salvare e caricare più oggetti nel file pickle? per dettagli su diversi modi per farlo. Personalmente mi piace la risposta di @Lutz Prechelt al meglio. Ecco adattato agli esempi qui:

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
Questo è raro per me perché immaginavo che ci sarebbe un modo più semplice per salvare un oggetto ... Qualcosa come 'saveobject (company1, c: \ mypythonobjects)
Peterstone

4
@Peterstone: se volessi solo memorizzare un oggetto, avresti bisogno solo della metà del codice del mio esempio - l'ho scritto apposta come ho fatto per mostrare come più di un oggetto poteva essere salvato (e successivamente riletto) da) lo stesso file.
martineau,

1
@Peterstone, c'è un'ottima ragione per separare le responsabilità. In questo modo non ci sono limiti al modo in cui vengono utilizzati i dati dal processo di decapaggio. Puoi memorizzarlo su disco o puoi anche inviarlo attraverso una connessione di rete.
Harald Scheirich,

3
@martinaeau, questo è stato in risposta all'osservazione di perstones su uno dovrebbe avere solo una funzione per salvare un oggetto su disco. La responsabilità dei sottaceti è solo quella di trasformare un oggetto in dati che possono essere gestiti come un pezzo. Scrivere cose su file è responsabilità degli oggetti file. Mantenendo le cose separate si consente un riutilizzo più elevato, ad es. La possibilità di inviare i dati decapitati attraverso una connessione di rete o di memorizzarli in un database, tutte le responsabilità sono separate dalla conversione effettiva dei dati <-> oggetto
Harald Scheirich,

1
Si elimina company1e company2. Perché non elimini Companye mostri anche cosa succede?
Mike McKerns,

49

Penso che sia un presupposto piuttosto forte supporre che l'oggetto sia un class. E se non fosse un class? Si presume anche che l'oggetto non sia stato definito nell'interprete. E se fosse stato definito nell'interprete? Inoltre, cosa succede se gli attributi vengono aggiunti in modo dinamico? Quando alcuni oggetti Python hanno degli attributi aggiunti alla loro __dict__creazione successiva, picklenon rispetta l'aggiunta di quegli attributi (cioè "dimentica" che sono stati aggiunti - perché pickleserializza facendo riferimento alla definizione dell'oggetto).

In tutti questi casi, picklee cPicklepuò fallirti in modo orribile.

Se stai cercando di salvare un object(creato arbitrariamente), dove hai degli attributi (o aggiunti nella definizione dell'oggetto, o in seguito) ... la tua scommessa migliore è usare dill, che può serializzare quasi tutto in Python.

Iniziamo con una lezione ...

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

Ora spegni e riavvia ...

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

Oops ... picklenon ce la faccio. Prova di Let dill. Inseriremo un altro tipo di oggetto (a lambda) per buona misura.

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

E ora leggi il file.

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

Funziona. La ragione picklefallisce e dillnon lo fa, è che dilltratta __main__come un modulo (per la maggior parte), e può anche decapare le definizioni delle classi invece di decapitarle per riferimento (come picklefa). La ragione per cui si dillpuò mettere sottaceto a lambdaè che gli dà un nome ... quindi può succedere la magia di decapaggio.

In realtà, c'è un modo più semplice per salvare tutti questi oggetti, specialmente se hai molti oggetti che hai creato. Scarica semplicemente l'intera sessione di Python e torna più tardi.

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

Ora spegni il computer, vai a goderti un espresso o altro e torna più tardi ...

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>

L'unico svantaggio principale è che dillnon fa parte della libreria standard di Python. Quindi se non puoi installare un pacchetto Python sul tuo server, non puoi usarlo.

Tuttavia, se sei in grado di installare pacchetti Python sul tuo sistema, puoi ottenere le ultime novità dillcon git+https://github.com/uqfoundation/dill.git@master#egg=dill. E puoi ottenere l'ultima versione rilasciata con pip install dill.


Ricevo un TypeError: __new__() takes at least 2 arguments (1 given)tentativo quando uso dill(che sembra promettente) con un oggetto piuttosto complesso che include un file audio.
MikeiLL,

1
@MikeiLL: stai ottenendo un TypeErrorquando fai cosa, esattamente? Questo di solito è un segno di avere il numero errato di argomenti durante l'istanza di un'istanza di classe. Se questo non fa parte del flusso di lavoro della domanda precedente, potresti pubblicarlo come un'altra domanda, inviarmelo tramite e-mail o aggiungerlo come problema sulla dillpagina di github?
Mike McKerns,

3
Per chiunque lo segua , ecco la domanda correlata che @MikeLL ha pubblicato - dalla risposta, apparentemente non era un dillproblema.
martineau,

dilMi dà MemoryErrorperò! così fa cPickle, picklee hickle.
Färid Alijani,

4

Puoi usare anycache per fare il lavoro per te. Considera tutti i dettagli:

  • Usa l' aneto come backend, che estende il picklemodulo Python per gestire lambdae tutte le belle funzionalità di Python.
  • Memorizza oggetti diversi in file diversi e li ricarica correttamente.
  • Limita la dimensione della cache
  • Consente la cancellazione della cache
  • Consente la condivisione di oggetti tra più esecuzioni
  • Consente il rispetto dei file di input che influenzano il risultato

Supponendo di avere una funzione myfuncche crea l'istanza:

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 chiama myfuncper la prima volta e sottrae il risultato a un file cachedirusando un identificatore univoco (a seconda del nome della funzione e dei suoi argomenti) come nome file. Ad ogni corsa consecutiva, l'oggetto in salamoia viene caricato. Se cachedirviene conservato tra le esecuzioni di Python, l'oggetto decapato viene prelevato dalla precedente esecuzione di Python.

Per ulteriori dettagli consultare la documentazione


Come si usa anycacheper salvare più di un'istanza, per esempio, di un classcontenitore come list(non era il risultato della chiamata di una funzione)?
martineau,

2

Esempio rapido usando company1dalla tua domanda, con 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"))

Tuttavia, come notato questa risposta , spesso il sottaceto fallisce. Quindi dovresti davvero usare 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"))
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.