Serialización y deserialización de objetos de Python Parte 1

La serialización y deserialización de objetos de Python es un aspecto importante de cualquier programa no trivial. Si en Python guarda algo en un archivo, si lee un archivo de configuración o si responde a una solicitud HTTP, realiza la serialización de objetos y la deserialización.. 

En un sentido, la serialización y la deserialización son las cosas más aburridas del mundo. ¿A quién le importan todos los formatos y protocolos? Solo desea persistir o transmitir algunos objetos de Python y recuperarlos posteriormente intactos. 

Esta es una manera muy saludable de mirar el mundo a nivel conceptual. Pero, en el nivel pragmático, el esquema de serialización, el formato o el protocolo que elija puede determinar qué tan rápido se ejecuta su programa, qué tan seguro es, cuánta libertad tiene para mantener su estado y qué tan bien interactuará con otros. sistemas. 

La razón por la que hay tantas opciones es que diferentes circunstancias requieren soluciones diferentes. No hay una talla para todos". En este tutorial de dos partes, repasaré las ventajas y desventajas de los esquemas de serialización y deserialización más exitosos, mostraré cómo usarlos y proporcionaré pautas para elegir entre ellos cuando se enfrente a un caso de uso específico..

Ejemplo de ejecución

En las siguientes secciones, serializaré y deserializaré los mismos gráficos de objetos de Python utilizando diferentes serializadores. Para evitar repeticiones, definiré estos gráficos de objetos aquí..

Gráfico de objeto simple

El gráfico de objeto simple es un diccionario que contiene una lista de enteros, una cadena, un flotante, un booleano y un Ninguno..

simple = dict (int_list = [1, 2, 3], text = "string", number = 3.44, boolean = True, none = None) 

Gráfico de objeto complejo

El gráfico de objetos complejos también es un diccionario, pero contiene una fecha y hora objeto y instancia de clase definida por el usuario que tiene una simple. atributo, que se establece en el gráfico de objeto simple.

desde datetime importe datetime clase A (objeto): def __init __ (self, simple): self.simple = simple def __eq __ (self, otro): si no hasattr (other, 'simple'): return False return self.simple == other.simple def __ne __ (self, other): si no hasattr (other, 'simple'): return True return self.simple! = other.simple complex = dict (a = A (simple), when = datetime (2016, 3, 7))

Conservar en vinagre

Pickle es un elemento básico. Es un formato nativo de serialización de objetos Python. La interfaz pickle proporciona cuatro métodos: volcado, volcado, carga y carga. los tugurio() El método se serializa en un archivo abierto (objeto tipo archivo). los deshecho() Método serializa a una cadena. los carga() El método se deserializa desde un objeto similar a un archivo abierto. los cargas () método deserializa desde una cadena.

Pickle admite de forma predeterminada un protocolo de texto, pero también tiene un protocolo binario, que es más eficiente, pero no legible para los humanos (útil para la depuración).

A continuación, se explica cómo encloslar un gráfico de objetos de Python en una cadena y en un archivo utilizando ambos protocolos..

importar cPickle as pickle.dumps (simple) "(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsS'number '\ np6 \ nF3.4399999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. "pickle.dumps (simple, protocol = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 (U \ x0441) \ x02U \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06neroq \ x05G @ \ x0b \ x0 \ u00pc.pf.pf 

La representación binaria puede parecer más grande, pero esto es una ilusión debido a su presentación. Al volcar en un archivo, el protocolo de texto es de 130 bytes, mientras que el protocolo binario es de solo 85 bytes..

pickle.dump (simple, abierto ('simple1.pkl', 'w')) pickle.dump (simple, abierto ('simple2.pkl', 'wb'), protocol = pickle.HIGHEST_PROTOCOL) ls -la sim *. * -rw-r - r-- 1 staff de gigi 130 Mar 9 02:42 simple1.pkl -rw-r - r-- 1 gigi staff 85 Mar 9 02:43 simple2.pkl

Desprenderse de una cadena es tan simple como:

x = pickle.loads ("(dp1 \ nS'text '\ np2 \ nS'string' \ np3 \ nsS'none '\ np4 \ nNsS'boolean' \ np5 \ nI01 \ nsSnnumber '\ np6 \ nF3.4399999999999999 \ nsS'int_list '\ np7 \ n (lp8 \ nI1 \ naI2 \ naI3 \ nas. ") afirman x == simple x = pickle.loads (' \ x80 \ x02 q \ x01 (U \ x04textq \ x02text \ x06stringq \ x03U \ x04noneq \ x04NU \ x07boolean \ x88U \ x06numberq \ x05G @ \ x0b \ x85 \ x1e \ xb8Q \ xeb \ x85K \ x08int_list_q] x \ x06 (K \ x01K \ x01K \ x02 p xpk / xpk).

Tenga en cuenta que pickle puede averiguar el protocolo automáticamente. No hay necesidad de especificar un protocolo incluso para el binario.

Desprender de un archivo es igual de fácil. Solo necesitas proporcionar un archivo abierto.

x = pickle.load (abierto ('simple1.pkl')) afirma x == simple x = pickle.load (abierto ('simple2.pkl')) afirma x == simple x = pickle.load (abierto ('simple2 .pkl ',' rb ')) afirma x == simple

De acuerdo con la documentación, se supone que debes abrir pepinillos binarios utilizando el modo 'rb', pero como puedes ver, funciona de cualquier manera.

Veamos cómo se ocupa Pickle con el gráfico de objetos complejos..

pickle.dumps (complex) "(dp1 \ nS'a '\ nccopy_reg \ n_reconstructor \ np2 \ n (c__main __ \ nA \ np3 \ nc__builtin __ \ nobject \ np4 \ nNtRp5 \ n (dp6 \ nSimple' \ nppc \ n dp8 \ nS'text '\ np9 \ nS'string' \ np10 \ nsS'none '\ np11 \ nNsS'boolean' \ np12 \ nI01 \ nsS'n'number '\ np13 \ nF3.439999999999 \ nsS'int_list' n (lp15 \ nI1 \ naI2 \ naI3 \ nassbsS'when '\ np16 \ ncdatetime \ ndatetime \ np17 \ n (S' \\ x07 \\ xe0 \\ x03 \\ x07 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 \\ x00 '\ ntRp18 \ ns. "pickle.dumps (complex, protocol = pickle.HIGHEST_PROTOCOL)' \ x80 \ x02 q \ x01 (U \ x01ac__main __ \ nA \ nq \ x02) \ x81q \ x03  q \ x04U \ x06simpleq \ x05 q \ x04textq \ x06stringq \ x08U \ x04noneq \ tNU \ x0 xlp. q \ x0b (K \ x01K \ x02K \ x03eusbU \ x04whenq \ x0ccdatetime \ ndatetime \ nq \ rU \ n \ x \ x07 \ xe0 \ x03 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 \ x00 .dump (complex, open ('complex1.pkl', 'w')) pickle.dump (complex, open ('complex2.pkl', 'wb'), protocol = pickle.HIGHEST_PROTOCOL) ls -la comp *. * -rw-r - r-- personal de 1 gigi 327 mar 9 9:58 complex1.pkl -rw-r - r-- personal de 1 gigi 171 09 de marzo 02:58 complex2.pkl

La eficiencia del protocolo binario es aún mayor con los gráficos de objetos complejos.

JSON

JSON (Notación de objetos de JavaScript) ha sido parte de la biblioteca estándar de Python desde Python 2.5. Lo consideraré un formato nativo en este punto. Es un formato basado en texto y es el rey no oficial de la web en lo que respecta a la serialización de objetos. Su sistema de tipos naturalmente modela JavaScript, por lo que es bastante limitado.. 

Vamos a serializar y deserializar los gráficos de objetos simples y complejos y ver qué sucede. La interfaz es casi idéntica a la interfaz de pickle. Tienes tugurio(), deshecho(), carga(), y cargas () funciones Pero, no hay protocolos para seleccionar, y hay muchos argumentos opcionales para controlar el proceso. Comencemos de forma simple al descargar el gráfico de objeto simple sin ningún argumento especial:

importar json imprimir json.dumps (simple) "texto": "cadena", "ninguno": nulo, "booleano": verdadero, "número": 3.44, "int_list": [1, 2, 3]

La salida parece bastante legible, pero no hay sangría. Para un gráfico de objeto más grande, esto puede ser un problema. Vamos a sangrar la salida:

print json.dumps (simple, sangría = 4) "text": "string", "none": null, "boolean": true, "number": 3.44, "int_list": [1, 2, 3]

Eso se ve mucho mejor. Vayamos al gráfico de objetos complejos..

json.dumps (complejo) -------------------------------------------- ------------------------------- TypeError Traceback (última llamada más reciente)  en () ----> 1 json.dumps (complex) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/__init__.pyc en vertederos (obj, skipkeys, asegurar_ascii, check_circular, allow_nan, cls, sangría, separadores, codificación, predeterminado, sort_keys, ** kw) 241 cls es Ninguno y sangría es Ninguno y separadores es Ninguno y 242 codificación == 'utf-8' y el valor predeterminado es Ninguno y no sort_keys y no kw): -> 243 return _default_encoder.encode (obj) 244 si cls es Ninguno: 245 cls = JSONEncoder /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework /Versions/2.7/lib/python2.7/json/encoder.pyc in encode (self, o) 205 # las excepciones no son tan detalladas. La lista de llamadas debe ser aproximadamente 206 # equivalente al PySequence_Fast que ".join () haría. -> 207 chunks = self.iterencode (o, _one_shot = True) 208 si no es isinstance (chunks, (list, tuple)) : 209 chunks = list (chunks) /usr/local/Cellar/python/2.7.10/Frameworks/Python.framework/Versions/2.7/lib/python2.7/json/encoder.pyc in iterencode (self, o, _one_shot ) 268 self.key_separator, self.item_separator, self.sort_keys, 269 self.skipkeys, _one_shot) -> 270 return _iterencode (o, 0) 271 272 def _make_iterencode (markers, _default, _encoder, _floatstr, / usr / local / Cellar / python / 2.7.10 / Frameworks / Python.framework / Versions / 2.7 / lib / python2.7 / json / encoder.pyc en forma predeterminada (self, o) 182 183 "" "-> 184 elevar TypeError ( repr (o) + "no es serializable JSON") 185 186 def encode (self, o): TypeError: <__main__.A object at 0x10f367cd0> no es JSON serializable

Whoa! Eso no se ve bien en absoluto. ¿Que pasó? El mensaje de error es que el objeto A no es serializable por JSON. Recuerde que JSON tiene un sistema de tipo muy limitado y no puede serializar las clases definidas por el usuario automáticamente. La forma de solucionarlo es subclasificar la clase JSONEncoder utilizada por el módulo json e implementar el defecto() que se llama cada vez que el codificador JSON se ejecuta en un objeto que no puede serializar. 

El trabajo del codificador personalizado es convertirlo en un gráfico de objetos Python que el codificador JSON puede codificar. En este caso tenemos dos objetos que requieren una codificación especial: el fecha y hora Objeto y la clase A. El siguiente codificador hace el trabajo. Cada objeto especial se convierte en un dictado donde la clave es el nombre del tipo rodeado de dunders (doble guión bajo). Esto será importante para la decodificación.. 

desde datetime importar datetime importar json clase CustomEncoder (json.JSONEncoder): def default (self, o): si es instancia (o, datetime): return '__datetime__': o.replace (microsecond = 0) .isoformat ()) return '__  __'. formato (o .__ clase __.__ nombre__): o .__ dict__

Intentemos nuevamente con nuestro codificador personalizado:

serialized = json.dumps (complex, indent = 4, cls = CustomEncoder) print serialized "a": "__A__": "simple": "text": "string", "none": null, "boolean ": verdadero," número ": 3.44," int_list ": [1, 2, 3]," cuando ": " __datetime__ ":" 2016-03-07T00: 00: 00 "

Esto es hermoso. El gráfico del objeto complejo se serializó correctamente y la información de tipo original de los componentes se conservó mediante las teclas: "__A__" y "__datetime__". Si usa dunders para sus nombres, entonces debe proponer una convención diferente para denotar tipos especiales.

Vamos a descifrar el gráfico de objeto complejo.

> deserialized = json.loads (serializado)> deserialized == complex False

Hmmm, la deserialización funcionó (sin errores), pero es diferente del gráfico de objeto complejo original que serializamos. Algo está mal. Echemos un vistazo al gráfico de objetos deserializados. Usaré el huella función de la huella módulo para impresión bonita.

> desde pprint import pprint> pprint (deserialized) u'a ': u' __ A__ ': u'simple': u'boolean ': True, u'int_list': [1, 2, 3], u 'ninguno': Ninguno, número ': 3.44, u'texto': cadena ', u'cuando': u '__ datetime__': u'2016-03-07T00: 00: 00 ' 

De acuerdo. El problema es que el módulo json no sabe nada sobre la clase A o incluso el objeto de fecha y hora estándar. Simplemente deserializa todo de forma predeterminada al objeto Python que coincide con su sistema de tipos. Para volver a un rico gráfico de objetos de Python, necesita una decodificación personalizada. 

No hay necesidad de una subclase de decodificador personalizado. los carga() y cargas () las funciones proporcionan el parámetro "object_hook" que le permite proporcionar una función personalizada que convierte los dicts en objetos. 

def decode_object (o): si '__A__' en o: a = A () a .__ dict __. update (o ['__ A__']) devuelve un elif '__datetime__' en o: return datetime.strptime (o ['__ datetime__' ], '% Y-% m-% dT% H:% M:% S') retorno o

Vamos a decodificar usando el decode_object () funciona como un parámetro para el cargas () parámetro object_hook.

> deserialized = json.loads (serializado, object_hook = decode_object)> imprimir deserialized u'a ': <__main__.A object at 0x10d984790>, u'when ': datetime.datetime (2016, 3, 7, 0, 0)> deserialized == complex True

Conclusión

En la primera parte de este tutorial, aprendió sobre el concepto general de serialización y deserialización de objetos de Python y exploró los entresijos de los objetos de Python utilizando Pickle y JSON.. 

En la segunda parte, aprenderá acerca de YAML, el rendimiento y las preocupaciones de seguridad, y una revisión rápida de esquemas de serialización adicionales..

Aprender Python

Aprende Python con nuestra completa guía de tutoriales de Python, ya sea que estés empezando o seas un programador experimentado que busca aprender nuevas habilidades..