pickle — Serialización de objetos¶
Propósito: | Serialización de objetos. |
---|
El módulo pickle
implementa un algoritmo para convertir un objeto
arbitrario Python en una serie de bytes. Este proceso es también llamado
serialización de objetos. El flujo de bytes que representa al objeto puede
ser transmitido o almacenado, y luego reconstruido para crear un nuevo objeto
con las mismas características.
Advertencia
La documentación para pickle
deja claro que no ofrece garantías de
seguridad. De hecho, la deserialización de puede ejecutar código
arbitrario. Ten cuidado al usar pickle
para la comunicación entre
procesos o el almacenamiento de datos, y no confíes en datos que no pueden
ser verificados como seguros. Ve el modulo hmac
para un ejemplo de
una manera segura para verificar la fuente de datos serializados.
Codificación y decodificación de datos en cadenas¶
Este primer ejemplo utiliza dumps()
para codificar una estructura de datos
como una cadena, luego imprime la cadena en la consola. Utiliza una estructura
de datos compuesta enteramente de tipos incorporados. Instancias de cualquier
clase puede ser serializados, como se ilustrará en un ejemplo más adelante.
import pickle
import pprint
data = [{'a': 'A', 'b': 2, 'c': 3.0}]
print('DATA:', end=' ')
pprint.pprint(data)
data_string = pickle.dumps(data)
print('PICKLE: {!r}'.format(data_string))
Por defecto, la serialización se escribirá en un formato binario más compatible cuando se comparte entre programas de Python 3.
$ python3 pickle_string.py
DATA: [{'a': 'A', 'b': 2, 'c': 3.0}]
PICKLE: b'\x80\x03]q\x00}q\x01(X\x01\x00\x00\x00cq\x02G@\x08\x00
\x00\x00\x00\x00\x00X\x01\x00\x00\x00bq\x03K\x02X\x01\x00\x00\x0
0aq\x04X\x01\x00\x00\x00Aq\x05ua.'
Una vez que los datos se serializan, se pueden escribir en un archivo, socket, pipe, etc. Más tarde, el archivo se puede leer y los datos se pueden deserializar para construir un nuevo objeto con los mismos valores.
import pickle
import pprint
data1 = [{'a': 'A', 'b': 2, 'c': 3.0}]
print('BEFORE: ', end=' ')
pprint.pprint(data1)
data1_string = pickle.dumps(data1)
data2 = pickle.loads(data1_string)
print('AFTER : ', end=' ')
pprint.pprint(data2)
print('SAME? :', (data1 is data2))
print('EQUAL?:', (data1 == data2))
El objeto recién construido es igual a, pero no el mismo, que el original.
$ python3 pickle_unpickle.py
BEFORE: [{'a': 'A', 'b': 2, 'c': 3.0}]
AFTER : [{'a': 'A', 'b': 2, 'c': 3.0}]
SAME? : False
EQUAL?: True
Trabajar con flujos¶
Además de dumps()
y loads()
, pickle
proporciona funciones de
conveniencia para trabajar con flujos tipo archivo. Es posible escribir varios
objetos en una flujo y luego leerlos de el flujo corriente sin saber de
antemano cuántos objetos se han escrito, o qué tan grandes son.
import io
import pickle
import pprint
class SimpleObject:
def __init__(self, name):
self.name = name
self.name_backwards = name[::-1]
return
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('preserve'))
data.append(SimpleObject('last'))
# Simulate a file.
out_s = io.BytesIO()
# Write to the stream
for o in data:
print('WRITING : {} ({})'.format(o.name, o.name_backwards))
pickle.dump(o, out_s)
out_s.flush()
# Set up a read-able stream
in_s = io.BytesIO(out_s.getvalue())
# Read the data
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print('READ : {} ({})'.format(
o.name, o.name_backwards))
El ejemplo simula flujos usando dos buffers BytesIO
. El primero recibe los
objetos serializados, y su valor se alimenta a un segundo de el cual load()
lee. Un simple formato de base de datos podría usar serialización para guardar
objetos, también. El módulo shelve
es una implementación.
$ python3 pickle_stream.py
WRITING : pickle (elkcip)
WRITING : preserve (evreserp)
WRITING : last (tsal)
READ : pickle (elkcip)
READ : preserve (evreserp)
READ : last (tsal)
Además de almacenar datos, la serialización es útil para la comunicación
inter-proceso. Por ejemplo, os.fork()
y os.pipe()
pueden ser
utilizados para establecer procesos de trabajo que leen instrucciones de
trabajo de un pipe y escriben los resultados en otro pipe. El código básico
para la gestión del grupo de trabajadores y el envío de trabajos y la recepción
de respuestas pueden ser reutilizados, ya que los objetos de trabajo y
respuesta no tienen que estar basados en una clase particular. Cuando utilices
pipes o sockets, no olvides vaciar después de serializar cada objeto, para
enviar los datos a través de la conexión al otro extremo. Ve el módulo
multiprocessing
para un gestor de trabajadores reutilizable.
Problemas al reconstruir objetos¶
Cuando se trabaja con clases personalizadas, la clase que está siendo serializada debe aparecer en el espacio de nombres del proceso leyendo la serialización. Sólo los datos de la instancia son serializados, no la definición de clase. El nombre de la clase es utilizado para encontrar el constructor para crear el nuevo objeto cuando se deserialice. El siguiente ejemplo escribe instancias de una clase en un archivo.
import pickle
import sys
class SimpleObject:
def __init__(self, name):
self.name = name
l = list(name)
l.reverse()
self.name_backwards = ''.join(l)
if __name__ == '__main__':
data = []
data.append(SimpleObject('pickle'))
data.append(SimpleObject('preserve'))
data.append(SimpleObject('last'))
filename = sys.argv[1]
with open(filename, 'wb') as out_s:
for o in data:
print('WRITING: {} ({})'.format(
o.name, o.name_backwards))
pickle.dump(o, out_s)
Cuando se ejecuta, la secuencia de comandos crea un archivo basado en el nombre dado como argumento en la línea de comando.
$ python3 pickle_dump_to_file_1.py test.dat
WRITING: pickle (elkcip)
WRITING: preserve (evreserp)
WRITING: last (tsal)
Un intento simplista de cargar los objetos serializados resultantes falla.
import pickle
import pprint
import sys
filename = sys.argv[1]
with open(filename, 'rb') as in_s:
while True:
try:
o = pickle.load(in_s)
except EOFError:
break
else:
print('READ: {} ({})'.format(
o.name, o.name_backwards))
Esta versión falla porque no hay una clase SimpleObject
disponible.
$ python3 pickle_load_from_file_1.py test.dat
Traceback (most recent call last):
File "pickle_load_from_file_1.py", line 15, in <module>
o = pickle.load(in_s)
AttributeError: Can't get attribute 'SimpleObject' on <module '_
_main__' from 'pickle_load_from_file_1.py'>
La versión corregida, que importa SimpleObject
de la secuencia de comandos
original, tiene éxito. Agregando esta declaración de importación al final de
la lista de importación permite que la secuencia de comandos encuentre la clase
y construya el objeto.
from pickle_dump_to_file_1 import SimpleObject
Ejecutar la secuencia de comandos modificada ahora produce los resultados deseados.
$ python3 pickle_load_from_file_2.py test.dat
READ: pickle (elkcip)
READ: preserve (evreserp)
READ: last (tsal)
Objetos no recuperables¶
No todos los objetos pueden ser serializados. Sockets, gestores de archivos,
conexiones de bases de datos, y otros objetos con estado de ejecución que
dependen del sistema operativo u otro proceso puede no ser posible guardar de
manera significativa. Los objetos que tienen atributos no serializables
pueden definir __getstate__()
y __setstate__()
para devolver un
subconjunto del estado de la instancia a ser serializada.
El método __getstate__()
debe devolver un objeto que contenga el estado
interno del objeto. Una forma conveniente de representar ese estado es con un
diccionario, pero el valor puede ser cualquier objeto serializable. El estado
se almacena y se pasa a __setstate__()
cuando el objeto se carga desde la
serialización.
import pickle
class State:
def __init__(self, name):
self.name = name
def __repr__(self):
return 'State({!r})'.format(self.__dict__)
class MyClass:
def __init__(self, name):
print('MyClass.__init__({})'.format(name))
self._set_name(name)
def _set_name(self, name):
self.name = name
self.computed = name[::-1]
def __repr__(self):
return 'MyClass({!r}) (computed={!r})'.format(
self.name, self.computed)
def __getstate__(self):
state = State(self.name)
print('__getstate__ -> {!r}'.format(state))
return state
def __setstate__(self, state):
print('__setstate__({!r})'.format(state))
self._set_name(state.name)
inst = MyClass('name here')
print('Before:', inst)
dumped = pickle.dumps(inst)
reloaded = pickle.loads(dumped)
print('After:', reloaded)
Este ejemplo usa un objeto State
separado para mantener el estado interno
de MyClass
. Cuando una instancia de MyClass
se carga desde una
serialización, __setstate__()
se pasa una instancia State
que se
utiliza para inicializar el objeto.
$ python3 pickle_state.py
MyClass.__init__(name here)
Before: MyClass('name here') (computed='ereh eman')
__getstate__ -> State({'name': 'name here'})
__setstate__(State({'name': 'name here'}))
After: MyClass('name here') (computed='ereh eman')
Advertencia
Si el valor devuelto es falso, entonces __setstate__()
no se llama
cuando el objeto se deserializa.
Referencias circulares¶
El protocolo pickle maneja automáticamente referencias circulares entre objetos, por lo que las estructuras de datos complejas no necesitan ningún manejo especial. Considera el grafo dirigido en the figure. Incluye varios ciclos, sin embargo, la estructura correcta puede ser serializada y luego deserializada.

Serializando una estructura de datos con ciclos¶
import pickle
class Node:
"""A simple digraph
"""
def __init__(self, name):
self.name = name
self.connections = []
def add_edge(self, node):
"Create an edge between this node and the other."
self.connections.append(node)
def __iter__(self):
return iter(self.connections)
def preorder_traversal(root, seen=None, parent=None):
"""Generator function to yield the edges in a graph.
"""
if seen is None:
seen = set()
yield (parent, root)
if root in seen:
return
seen.add(root)
for node in root:
recurse = preorder_traversal(node, seen, root)
for parent, subnode in recurse:
yield (parent, subnode)
def show_edges(root):
"Print all the edges in the graph."
for parent, child in preorder_traversal(root):
if not parent:
continue
print('{:>5} -> {:>2} ({})'.format(
parent.name, child.name, id(child)))
# Set up the nodes.
root = Node('root')
a = Node('a')
b = Node('b')
c = Node('c')
# Add edges between them.
root.add_edge(a)
root.add_edge(b)
a.add_edge(b)
b.add_edge(a)
b.add_edge(c)
a.add_edge(a)
print('ORIGINAL GRAPH:')
show_edges(root)
# Pickle and unpickle the graph to create
# a new set of nodes.
dumped = pickle.dumps(root)
reloaded = pickle.loads(dumped)
print('\nRELOADED GRAPH:')
show_edges(reloaded)
Los nodos recargados no son el mismo objeto, pero la relación entre los nodos
se mantiene y solo una copia del objeto con múltiples referencias es
deserializado. Ambas declaraciones pueden ser verificado examinando los
valores id()
para los nodos antes y después de ser pasado por pickle.
$ python3 pickle_cycle.py
ORIGINAL GRAPH:
root -> a (4315798272)
a -> b (4315798384)
b -> a (4315798272)
b -> c (4315799112)
a -> a (4315798272)
root -> b (4315798384)
RELOADED GRAPH:
root -> a (4315904096)
a -> b (4315904152)
b -> a (4315904096)
b -> c (4315904208)
a -> a (4315904096)
root -> b (4315904152)
Ver también
- Documentación de la biblioteca estándar para pickle
- PEP 3154 – Protocolo de pickle versión 4
shelve
– El móduloshelve
usapickle
paralmacenar a datos en una base de datos DBM.- Pickle: Un idioma de pila interesante. – por Alexandre Vassalotti