Guardar un objeto (persistencia de datos)

He creado un objeto como este:

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

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

Podría usar el módulo pickle en la biblioteca estándar. Aquí hay una aplicación elemental de esto a tu 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 escribir una utilidad simple como la siguiente, que abre un archivo y le escribe un único objeto:

 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:

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

cPickle (o _pickle ) vs pickle

Casi siempre es preferible usar el módulo cPickle lugar de pickle porque 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 cambia la statement de import a esto:

 import cPickle as pickle 

En Python 3, cPickle cambió _pickle nombre a _pickle , pero ya no es necesario hacerlo ya que el módulo pickle ahora lo hace de forma automática. Consulte ¿Qué diferencia hay entre pickle y _pickle en python 3? .

El resumen es que puede usar algo como lo siguiente para asegurarse de que su código siempre usará 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)

pickle puede leer y escribir archivos en varios formatos diferentes, específicos de Python, llamados protocolos . “Protocolo versión 0” es ASCII y, por lo tanto, “legible por humanos”. Las versiones> 1 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 0 Protocolo, pero en Python 3.6, es la versión 3 del Protocolo. En Python 3.x el módulo tenía un pickle.DEFAULT_PROTOCOL agregado, pero eso no existe en Python 2.

Afortunadamente, hay una taquigrafía para escribir pickle.HIGHEST_PROTOCOL en cada llamada (suponiendo que es lo que quiere y normalmente hace), solo use el número literal -1 , similar a la 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) 

Usted puede simplemente escribir:

 pickle.dump(obj, output, -1) 

De cualquier manera, solo habrías especificado el protocolo una vez si hubieras creado un objeto Pickler para usar en múltiples operaciones de decapado:

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

Objetos multiples

Si bien un archivo de pickle puede contener cualquier número de objetos decapados, como se muestra en las muestras anteriores, cuando hay un número desconocido de ellos, a menudo es más fácil almacenarlos en algún tipo de contenedor de tamaño variable, como una list , una tuple o dict y escribirlos todos en el 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 restaurar la lista y todo lo que hay 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 volver a cargarlas más tarde (aunque hacerlo sin esa información es posible, requiere un código ligeramente especializado). Vea las respuestas a la pregunta relacionada ¿ Guardando y cargando múltiples objetos en un archivo pickle? para detalles sobre diferentes maneras de hacer esto. Personalmente me gusta la respuesta de @Lutz Prechelt la mejor. 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)) 

Creo que es una suposición bastante fuerte asumir que el objeto es una class . ¿Y si no es una class ? También existe la suposición de que el objeto no se definió en el intérprete. ¿Y si se definiera en el intérprete? Además, ¿y si los atributos se agregaran dinámicamente? Cuando algunos objetos de Python tienen atributos agregados a su __dict__ después de la creación, pickle no respeta la adición de esos atributos (es decir, se “olvida” de que se agregaron, porque pickle serializa por referencia a la definición del objeto).

En todos estos casos, pickle y cPickle pueden cPickle horriblemente.

Si está buscando guardar un object (creado arbitrariamente), donde tiene atributos (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 apaga, y reinicia …

 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 "", line 2, in  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 … pickle no puede manejarlo. Probemos el dill . Lanzaremos otro tipo de objeto (una 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 por la que pickle falla, y dill no, es que dill trata a __main__ como un módulo (en su mayor parte), y también puede pickle definiciones de clase en lugar de declive por referencia (como pickle ). La razón por la que dill puede encurtir a un lambda es que le da un nombre … para que la magia de encurtido pueda suceder.

En realidad, hay una forma más fácil de guardar todos estos objetos, especialmente si tienes muchos objetos que has 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 apaga la computadora, ve a disfrutar de un espresso o lo que sea, y vuelve 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  at 0x1065f2938> 

El único inconveniente importante es que el dill no 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 la última git+https://github.com/uqfoundation/dill.git@master#egg=dill dill con git+https://github.com/uqfoundation/dill.git@master#egg=dill . Y puede obtener la última versión publicada con pip install dill .

Puedes usar anycache para hacer el trabajo por ti. Considera todos los detalles:

  • Utiliza dill como backend, que extiende el módulo pickle python para manejar lambda y todas las características agradables de python.
  • Almacena diferentes objetos en diferentes archivos y los vuelve a cargar correctamente.
  • Limita el tamaño del 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 myfunc que 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 a myfunc la primera vez y escurre el resultado a un archivo en cachedir usando un identificador único (según el nombre de la función y sus argumentos) como nombre de archivo. En cualquier ejecución consecutiva, el objeto encurtido se carga. Si el cachedir se 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.