pickle – Serialización de objetos



Versión: 2.x, 3.x.

Introducción

El proceso de serialización consiste en transformar un objeto determinado en un texto en base a un lenguaje específico, para ser almacenado o bien transferido y, por último, restablecido al objeto original. Por ejemplo, guardar una lista de Python en un archivo de texto o base de datos, y luego cargarlo cuando sea necesario. Formatos comunes entre los distintos lenguajes de programación incluyen XML y JSON.

En Python existen distintos tipos de serialización, accesibles a través de la librería estándar: marshal, shelve y pickle. Todos permiten serializar objetos independientemente de la plataforma, pero con algunas diferencias.

El módulo marshal es de lo más primitivo, y existe principalmente para el manejo de archivos .pyc. A diferencia de pickle, no garantiza la compatibilidad y consistencia con versiones anteriores; incluso, puede darse la variación del formato binario que utiliza. Además, es menos eficaz y eficiente, pues no soporta la cantidad de objetos que permite pickle, ni los procesa con la misma velocidad.

shelve es un módulo que provee un objeto denominado Shelf, basado en diccionarios, una interfaz de mayor nivel basada en pickle que sería tema de conversación para otro artículo.

A diferencia de los previamente mencionados XML y JSON, los módulos marshal, shelve y pickle son específicos de Python, por lo que se ajustan mejor a las necesidades del lenguaje. Si tu objetivo es intercambiar objetos entre lenguajes, este no es el artículo adecuado, y querrás darle una leída a la documentación de json y xml.

Protocolos

El módulo pickle incluye distintos tipos de serialización, a los que se refiere como protocolos. Cada vez que se realizan cambios que comprometen la compatibilidad con versiones anteriores, se crea un nuevo protocolo, identificado con un número. Por ende, el protocolo con mayor número resulta ser el más optimizado y preferente (mientras que la compatibilidad con versiones anteriores no se incluya en nuestros intereses). Hasta la versión 3.4, están a disposición cinco protocolos (del 0 al 4). Usuarios de Python 2 o bien de Python 3 que requieran compatibilidad con esta versión querrán optar por el protocolo número 2, que fue introducido en la versión 2.3. Raramente se le dará utilidad a los protocolos 0 y 1 ya que se implementaron en versiones iniciales del lenguaje. En todas las versiones de Python 3, el protocolo por defecto es el número 3, el cual fue introducido en la versión 3.0 y no puede ser utilizado por versiones anteriores. En la versión 3.4 se incluye el protocolo número 4, detallado en el documento PEP 3154.

cPickle

Python 2 cuenta con un módulo análogo llamado cPickle; como lo indica el nombre, una implementación del sistema de serialización de pickle escrita en C. Por esta característica, resulta ser más eficiente en cuanto a velocidad que el módulo tradicional escrito en Python (en ocasiones, hasta 1.000 veces más rápido).

Son poco usuales las circunstancias en las que pickle se prefiere por sobre cPickle. La principal diferencia entre ambos módulos es que este último no puede ser utilizado para crear clases que hereden desde Pickler o Unpickler.

En Python 3 cPickle fue renombrado a _pickle y es utilizado automáticamente por el módulo pickle siempre que sea posible. Usuarios de la versión 3.x no deberían utilizar _pickle directamente.

Serialización de objetos

Sin más preámbulos, las funciones principales son dump, dumps, load y loads. Las primeras dos permiten serializar un objeto, mientras que las últimas realizan el procedimiento contrario. Veamos un ejemplo.

Por lo explicado anteriormente, usuarios de Python 2 opten por utilizar cPickle siempre que sea posible.

from cPickle import dump, dumps, load, loads

En Python 3, simplemente pickle.

from pickle import dump, dumps, load, loads

A continuación se serializa (pickle) una lista utilizando la función dumps y luego se obtiene nuevamente (unpickle), a partir de la cadena generada, con la función loads.

obj = [True, 100, "Recursos Python", -6.2]
pickled_obj = dumps(obj)
print(pickled_obj)
unpickled_obj = loads(pickled_obj)
print(unpickled_obj)

Salida en Python 3:

b'\x80\x03]q\x00(\x88KdX\x0f\x00\x00\x00Recursos Pythonq\x01G\xc0\x18\xcc\xcc\xcc\xcc\xcc\xcde.'
[True, 100, 'Recursos Python', -6.2]

Python 2:

(lp1
I01
aI100
aS'Recursos Python'
p2
aF-6.2000000000000002
a.
[True, 100, 'Recursos Python', -6.2]

Las distintas versiones producen diferentes cadenas al momento de serializar un objeto, debido al protocolo que cada una utiliza por defecto.

Como puede observarse, las funciones dumps y loads trabajan directamente con el objeto serializado, a diferencia de dump() y load(), que escriben y leen en un archivo determinado. Véase el siguiente ejemplo que almacena el objeto en un archivo.

obj = [True, 100, "Recursos Python", -6.2]

with open("pickled_obj", "wb") as f:
    dump(obj, f)

with open("pickled_obj", "rb") as f:
    unpickled_obj = load(f)

# Verificar que sean iguales.
print(unpickled_obj == obj)  # True

El módulo cuenta con una constante que indica la cantidad de protocolos disponibles, incluyendo el cero.

# Python 2.
>>> import cPickle
>>> cPickle.HIGHEST_PROTOCOL
2

# Python 3.
>>> import pickle
>>> pickle.HIGHEST_PROTOCOL
4

Adicionalmente, la versión 3.x incluye la constante DEFAULT_PRTOCOL, que indica el protocolo utilizado por defecto.

>>> pickle.DEFAULT_PROTOCOL
3

Para utilizar un protocolo diferente al empleado por defecto, se utiliza el parámetro protocol en las funciones dump y dumps. Al momento de de-serializar un objeto, las funciones load y loads determinan automáticamente el protocolo que se ha utilizado. Por ejemplo, el siguiente código es ejecutado por Python 3.4, serializa un objeto y lo almacena en un archivo utilizando el protocolo número 2.

from pickle import dump

obj = [True, 100, "Recursos Python", -6.2]

with open("compatible_object", "wb") as f:
    dump(obj, f, protocol=2)

Al utilizar dicho protocolo, mantiene la compatibilidad con versiones anteriores. Por ende, puede ser leído por el siguiente script ejecutado por Python 2.7.9.

from cPickle import load

with open("compatible_object", "rb") as f:
    unpickled_obj = load(f)

print(unpickled_obj)

Imprime:

[True, 100, u'Recursos Python', -6.2]

Intentos por leer objetos serializados por un protocolo no soportado lanzarán una excepción.

ValueError: unsupported pickle protocol

Python 3 incluye un nuevo parámetro opcional a las cuatro funciones descritas anteriormente, llamado fix_imports (True por defecto). Determina si, al leer o escribir un objeto utilizando un protocolo compatible con Python 2 (es decir, menor a 3), se convertirán los nombres de módulos que hayan sido modificados. Por ejemplo, queue será reemplazado por Queue. Además, los parámetros encoding y errors ("ASCII" y "strict" por defecto) en las funciones load y loads indican la codificación en la que serán leídas las cadenas generadas por el módulo pickle en la versión 2.x.

La documentación oficial incluye una lista de los objetos que pueden ser serializados. Instancias de clases están perfectamente soportadas, sin embargo, para modificar el comportamiento al momento de ser serializadas, véase este apartado.



Deja un comentario