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?

Author: H. Tao, 2010-12-25

3 answers

Puede usar el módulo pickle en la biblioteca estándar. He aquí una aplicación elemental de la misma a 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 podría escribir una 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')

Actualización:

Dado 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 utilizar el cPickle módulo en 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 it no podría ser más fácil, simplemente cambie la instrucción import a esto:

import cPickle as pickle

En Python 3, cPickle fue renombrado _pickle, pero hacer esto ya no es necesario ya que el módulo pickle ahora lo hace automáticamente-ver Qué diferencia 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 tanto en Python 2 como en 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 . La "versión 0 del protocolo" es ASCII y por lo tanto "legible por humanos". Versiones > 1 son binarios y el más alto 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 la versión predeterminada era Protocol version 0, pero en Python 3.6, es Protocol version 3. En Python 3.x el módulo tenía un pickle.DEFAULT_PROTOCOL añadido, pero eso no existe en Python 2.

Afortunadamente hay abreviatura para escribir pickle.HIGHEST_PROTOCOL en cada llamada (suponiendo que eso es lo que quieres, y generalmente lo haces)-solo usa el número literal -1. Así que, en su lugar de la escritura:

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

Puedes escribir:

pickle.dump(obj, output, -1)

De cualquier manera, solo tendría que especificar el protocolo una vez si creó un objeto Pickler para usar en múltiples operaciones de pickle:

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

Múltiples objetos

Mientras que un archivo de pepinillos puede contener cualquier número de objetos en escabeche, como se muestra en las muestras anteriores, cuando hay un número desconocido de ellos, a menudo es más fácil almacenarlos todos en algún tipo de contenedor de tamaño variable, como list, 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 cargarlas de nuevo más tarde (aunque hacerlo sin esa información es posible, requiere un código ligeramente especializado). Vea las respuestas a la pregunta relacionada ¿Guardar y cargar varios objetos en el archivo pickle? para más 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))
 324
Author: martineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-21 12:27:19

Creo que es una suposición bastante fuerte asumir que el objeto es un class. ¿Y si no es un class? También está la suposición de que el objeto no fue definido en el intérprete. ¿Y si se definiera en el intérprete? Además, ¿qué pasaría si los atributos se agregaran dinámicamente? Cuando algunos objetos python tienen atributos agregados a su __dict__ después de la creación, pickle no respeta la adición de esos atributos ( es decir, 'olvida' que fueron agregados because porque pickle serializa por referencia a la definición del objeto).

En todos estos casos, pickle y cPickle puede fallar terriblemente.

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

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

Oops {pickle no puedo manejarlo. Probemos dill. Agregaremos otro tipo de objeto (a lambda) para una buena medida.

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 lea 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 pickle falla, y dill no, es que dill trata __main__ como un módulo (la mayor parte), y también puede pickle definiciones de clase en lugar de decapado por referencia (como pickle hace). La razón por la que dill puede encurtir un lambda es que le da un nombre then entonces la magia del encurtido puede suceder.

En realidad, hay una manera más fácil de guardar todos estos objetos, especialmente si tienes muchos objetos que has creado. Simplemente volcar toda la sesión de Python, y volver 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, vaya a disfrutar de un espresso o lo que sea, y vuelva 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 dill no es parte de la biblioteca estándar de python. Así que si no puedes instalar un paquete python en tu servidor, entonces no puedes usarlo.

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

 40
Author: Mike McKerns,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-13 00:54:58

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

  • Utiliza dill como motor, que extiende el módulo python pickle para manejar lambda y todos los características de python.
  • Almacena diferentes objetos en diferentes archivos y los recarga correctamente.
  • Limita el tamaño de la caché
  • Permite borrar la caché
  • Permite compartir objetos entre varias ejecuciones
  • Permite el respeto de los archivos de entrada que influir en el resultado

Asumiendo 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 encurte el resultado a un archivo en cachedir usando 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 decapado. Si el cachedir se conserva entre ejecuciones de python, el objeto pickled se toma de la ejecución anterior de python.

Para más detalles, véase la documentación

 2
Author: c0fec0de,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-19 20:27:41