Guardar un objeto (persistencia de datos)

233

He creado un objeto como este:

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

Me gustaría guardar este objeto. ¿Cómo puedo hacer eso?

Peterstone
fuente
1
Vea el ejemplo para las personas que vienen aquí para un ejemplo simple de cómo usar pickle.
Martin Thoma
@MartinThoma: ¿Por qué (aparentemente) prefiere esa respuesta a la respuesta aceptada (de la pregunta vinculada )?
Martineau
En el momento en que me vinculé, la respuesta aceptada no tenía protocol=pickle.HIGHEST_PROTOCOL. Mi respuesta también da alternativas al pepinillo.
Martin Thoma

Respuestas:

449

Puede usar el picklemódulo en la biblioteca estándar. Aquí hay una aplicación básica para su ejemplo:

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

También puede definir su propia utilidad simple como la siguiente que abre un archivo y escribe un solo objeto en él:

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

Actualizar

Dado que esta es una respuesta tan popular, me gustaría tocar algunos temas de uso ligeramente avanzados.

cPickle(o _pickle) vspickle

Casi siempre es preferible usar el cPicklemódulo en lugar de hacerlo pickleporque el primero está escrito en C y es mucho más rápido. Hay algunas diferencias sutiles entre ellos, pero en la mayoría de las situaciones son equivalentes y la versión C proporcionará un rendimiento muy superior. Cambiar a él no podría ser más fácil, solo cambie la importdeclaración a esto:

import cPickle as pickle

En Python 3, cPicklese le cambió el nombre _pickle, pero hacerlo ya no es necesario ya que el picklemódulo ahora lo hace automáticamente. ¿Qué diferencia hay entre pickle y _pickle en python 3? .

El resumen es que podría usar algo como lo siguiente para asegurarse de que su código siempre use la versión C cuando esté disponible en Python 2 y 3:

try:
    import cPickle as pickle
except ModuleNotFoundError:
    import pickle

Formatos de flujo de datos (protocolos)

picklepuede leer y escribir archivos en varios formatos diferentes, específicos de Python, llamados protocolos como se describe en la documentación , "Protocolo versión 0" es ASCII y por lo tanto "legible por humanos". Las versiones> 0 son binarias y la más alta disponible depende de la versión de Python que se esté utilizando. El valor predeterminado también depende de la versión de Python. En Python 2, el valor predeterminado era la versión del Protocolo 0, pero en Python 3.8.1, es la versión del Protocolo 4. En Python 3.x, el módulo tenía un pickle.DEFAULT_PROTOCOLagregado, pero eso no existe en Python 2.

Afortunadamente, hay una forma abreviada de escribir pickle.HIGHEST_PROTOCOLen cada llamada (suponiendo que eso es lo que desea, y generalmente lo hace), solo use el número literal -1, similar a hacer referencia al último elemento de una secuencia a través de un índice negativo. Entonces, en lugar de escribir:

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

Solo puedes escribir:

pickle.dump(obj, output, -1)

De cualquier manera, solo habría especificado el protocolo una vez si hubiera creado un Picklerobjeto para usar en múltiples operaciones de encurtido:

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

Nota : Si se encuentra en un entorno que ejecuta diferentes versiones de Python, entonces probablemente quiera usar explícitamente (es decir, código rígido) un número de protocolo específico que todos puedan leer (las versiones posteriores generalmente pueden leer archivos producidos por versiones anteriores) .

Objetos múltiples

Mientras que un archivo de salmuera puede contener cualquier número de objetos en escabeche, como se muestra en los ejemplos anteriores, cuando hay un número desconocido de ellos, a menudo es más fácil de almacenar todos ellos en algún tipo de recipiente de tamaño variable, como una list, tupleo dictya escribir todos ellos al archivo en una sola llamada:

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

y restaure la lista y todo en ella más tarde con:

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

La principal ventaja es que no necesita saber cuántas instancias de objetos se guardan para cargarlas de nuevo más tarde (aunque hacerlo sin esa información es posible, requiere un código ligeramente especializado). Ver las respuestas a la pregunta relacionada ¿ Guardar y cargar varios objetos en un archivo pickle? para obtener detalles sobre las diferentes formas de hacer esto. Personalmente, me gusta la respuesta de @Lutz Prechelt . Aquí está adaptado a los ejemplos aquí:

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
fuente
1
Esto es raro para mí porque imaginé que habría una manera más fácil de guardar un objeto ... Algo como 'saveobject (empresa1, c: \ mypythonobjects)
Peterstone
44
@Peterstone: si solo quisiera almacenar un objeto, solo necesitaría la mitad de la cantidad de código que en mi ejemplo; lo escribí a propósito como lo hice para mostrar cómo se puede guardar más de un objeto (y luego volver a leerlo de) el mismo archivo.
Martineau
1
@Peterstone, hay una muy buena razón para la separación de responsabilidades. De esta manera, no hay limitación sobre cómo se utilizan los datos del proceso de decapado. Puede almacenarlo en un disco o también puede enviarlo a través de una conexión de red.
Harald Scheirich
3
@martinaeau, esto fue en respuesta a la observación de perstones sobre que uno debería tener una sola función para guardar un objeto en el disco. La responsabilidad de los encurtidos es solo convertir un objeto en datos que puedan manejarse como un fragmento. Escribir cosas en el archivo es responsabilidad de los objetos del archivo. Al mantener las cosas separadas, se permite una mayor reutilización, por ejemplo, poder enviar los datos encurtidos a través de una conexión de red o almacenarlos en una base de datos, todas las responsabilidades se separan de la conversión real de datos <-> objeto
Harald Scheirich
1
Eliminas company1y company2. ¿Por qué no eliminas Companyy muestras lo que sucede también?
Mike McKerns
49

Creo que es una suposición bastante fuerte suponer que el objeto es a class. ¿Qué pasa si no es un class? También existe la suposición de que el objeto no estaba definido en el intérprete. ¿Qué pasa si se definió en el intérprete? Además, ¿qué pasa si los atributos se agregaron dinámicamente? Cuando algunos objetos de Python tienen atributos agregados a su __dict__creación posterior, pickleno respeta la adición de esos atributos (es decir, 'olvida' que se agregaron, porque pickleserializa por referencia a la definición del objeto).

En todos estos casos, pickley cPicklepuede fallar horriblemente.

Si está buscando guardar un object(creado arbitrariamente), donde tiene atributos (ya sea agregados en la definición del objeto o después) ... su mejor opción es usar dill, que puede serializar casi cualquier cosa en Python.

Comenzamos con una clase ...

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

Ahora apague y reinicie ...

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

Vaya ... pickleno puedo manejarlo. Try de Let dill. Agregaremos otro tipo de objeto (a lambda) por si acaso.

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

Y ahora lee el archivo.

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

Funciona. La razón picklefalla, y dillno lo hace, es que se dilltrata __main__como un módulo (en su mayor parte), y también puede encurtir definiciones de clase en lugar de encurtir por referencia (como lo picklehace). La razón por la que se dillpuede lambdaencurtir es porque le da un nombre ... entonces puede ocurrir la magia de encurtido.

En realidad, hay una manera más fácil de guardar todos estos objetos, especialmente si tiene muchos objetos que ha creado. Simplemente descargue toda la sesión de Python y vuelva a ella más tarde.

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

Ahora apague su computadora, disfrute de un espresso o lo que sea, y regrese más tarde ...

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>

El único inconveniente importante es que dillno forma parte de la biblioteca estándar de Python. Entonces, si no puede instalar un paquete de Python en su servidor, entonces no puede usarlo.

Sin embargo, si puede instalar paquetes de Python en su sistema, puede obtener lo último dillcon git+https://github.com/uqfoundation/dill.git@master#egg=dill. Y puede obtener la última versión lanzada con pip install dill.

Mike McKerns
fuente
Recibo un mensaje TypeError: __new__() takes at least 2 arguments (1 given)cuando intento usar dill(que parece prometedor) con un objeto bastante complejo que incluye un archivo de audio.
MikeiLL
1
@MikeiLL: ¿Recibes un TypeErrorcuando haces qué, exactamente? Eso suele ser una señal de tener un número incorrecto de argumentos al crear instancias de una instancia de clase. Si esto no es parte del flujo de trabajo de la pregunta anterior, ¿podría publicarlo como otra pregunta, dillenviármelo por correo electrónico o agregarlo como un problema en la página de Github?
Mike McKerns
3
Para cualquiera que lo siga, esta es la pregunta relacionada que @MikeLL publicó: a partir de la respuesta, aparentemente no fue un dillproblema.
Martineau 02 de
dil¡ MemoryErrorAunque me da ! también lo hace cPickle, pickley hickle.
Färid Alijani
4

Puede usar cualquier caché para hacer el trabajo por usted. Considera todos los detalles:

  • Utiliza eneldo como backend, que extiende el picklemódulo de Python para manejar lambday todas las características agradables de Python.
  • Almacena diferentes objetos en diferentes archivos y los vuelve a cargar correctamente.
  • Limita el tamaño de caché
  • Permite borrar el caché
  • Permite compartir objetos entre múltiples ejecuciones
  • Permite el respeto de los archivos de entrada que influyen en el resultado.

Suponiendo que tiene una función myfuncque crea la instancia:

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 llama myfuncpor primera vez y selecciona el resultado en un archivo cachedirutilizando un identificador único (dependiendo del nombre de la función y sus argumentos) como nombre de archivo. En cualquier ejecución consecutiva, se carga el objeto encurtido. Si cachedirse conserva entre las ejecuciones de Python, el objeto encurtido se toma de la ejecución anterior de Python.

Para más detalles ver la documentación

c0fec0de
fuente
¿Cómo se usaría anycachepara guardar más de una instancia de, digamos, un classcontenedor como un list(que no fue el resultado de llamar a una función)?
Martineau
2

Ejemplo rápido usando company1de su pregunta, 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"))

Sin embargo, como señaló esta respuesta , el pepinillo a menudo falla. Así que realmente deberías usarlo 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
fuente