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

Esta es la segunda parte de un tutorial sobre cómo serializar y deserializar objetos de Python. En la primera parte, aprendiste lo básico y luego te sumergiste en los entresijos de Pickle y JSON.. 

En esta parte, explorará YAML (asegúrese de tener el ejemplo en ejecución de la primera parte), analizará las consideraciones de rendimiento y seguridad, obtendrá una revisión de formatos de serialización adicionales y finalmente aprenderá cómo elegir el esquema correcto..

YAML

YAML es mi formato favorito. Es un formato de serialización de datos amigable para el ser humano. A diferencia de Pickle y JSON, no es parte de la biblioteca estándar de Python, por lo que necesita instalarlo:

pip instalar yaml

El módulo yaml tiene solo carga() y tugurio() funciones Por defecto trabajan con cadenas como cargas () y deshecho(), pero puede tomar un segundo argumento, que es una secuencia abierta y luego puede volcar / cargar a / desde archivos.

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

Observe cómo se compara YAML legible con Pickle o incluso con JSON. Y ahora, para la parte más interesante de YAML: ¡entiende los objetos de Python! No hay necesidad de codificadores y decodificadores personalizados. Aquí está la serialización / deserialización compleja utilizando YAML:

> serialized = yaml.dump (complex)> print serialized a: !! python / object: __ main __. A simple: boolean: true int_list: [1, 2, 3] none: null number: 3.44 text: string when: 2016- 03-07 00:00:00> deserialized = yaml.load (serializado)> deserialized == complex True

Como puede ver, YAML tiene su propia notación para etiquetar objetos de Python. La salida es todavía muy legible para los humanos. El objeto datetime no requiere ningún etiquetado especial porque YAML es inherentemente compatible con objetos datetime. 

Actuación

Antes de comenzar a pensar en el desempeño, debe pensar si el desempeño es una preocupación en absoluto. Si serializa / deserializa una pequeña cantidad de datos con poca frecuencia (por ejemplo, al leer un archivo de configuración al comienzo de un programa), el rendimiento no es realmente una preocupación y puede seguir adelante..

Pero, suponiendo que haya perfilado su sistema y descubrió que la serialización y / o la deserialización están causando problemas de rendimiento, aquí están las cosas que debe abordar.

Hay dos aspectos para el rendimiento: qué tan rápido puede serializar / deserializar, y qué tan grande es la representación serializada?

Para probar el rendimiento de los diversos formatos de serialización, crearé una estructura de datos bastante grande y la serializaré / deserializaré utilizando Pickle, YAML y JSON. los big_data lista contiene 5,000 objetos complejos.

big_data = [dict (a = simple, when = datetime.now (). replace (microsecond = 0)) para i en el rango (5000)]

Conservar en vinagre

Voy a usar IPython aquí por su conveniente %cronométralo Función mágica que mide los tiempos de ejecución..

importar cPickle as pickle In [190]:% timeit serialized = pickle.dumps (big_data) 10 loops, el mejor de 3: 51 ms por loop In [191]:% timeit deserialized = pickle.loads (serializado) 10 loops, best of 3: 24.2 ms por bucle In [192]: deserializado == big_data Out [192]: True In [193]: len (serializado) Out [193]: 747328

El pickle predeterminado tarda 83.1 milisegundos en serializarse y 29.2 milisegundos en deserializarse, y el tamaño serializado es de 747,328 bytes..

Probemos con el protocolo más alto..

En [195]:% timeit serialized = pickle.dumps (big_data, protocol = pickle.HIGHEST_PROTOCOL) 10 bucles, el mejor de 3: 21.2 ms por bucle In [196]:% timeit deserialized = pickle.loads (serializado) 10 bucles lo mejor de 3: 25.2 ms por bucle Entrada [197]: len (serializado) Salida [197]: 394350

Resultados interesantes. El tiempo de serialización se redujo a solo 21,2 milisegundos, pero el tiempo de deserialización aumentó un poco a 25,2 milisegundos. El tamaño serializado se redujo significativamente a 394,350 bytes (52%).

JSON

En [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 loops, el mejor de 3: 34.7 ms por loop In [253]% timeit deserialized = json.loads (serialized, object_hook = decode_object) 10 loops, lo mejor de 3: 148 ms por bucle Entrada [255]: len (serializado) Salida [255]: 730000

De acuerdo. El rendimiento parece ser un poco peor que Pickle para la codificación, pero mucho, mucho peor para la descodificación: 6 veces más lento. ¿Que esta pasando? Este es un artefacto de la object_hook función que debe ejecutarse en cada diccionario para comprobar si necesita convertirlo en un objeto. Correr sin el enganche de objetos es mucho más rápido.

% timeit deserialized = json.loads (serializado) 10 bucles, el mejor de 3: 36.2 ms por bucle

La lección aquí es que al serializar y deserializar a JSON, considere cuidadosamente cualquier codificación personalizada, ya que pueden tener un gran impacto en el rendimiento general..

YAML

En [293]:% timeit serialized = yaml.dump (big_data) 1 bucles, lo mejor de 3: 1.22 s por loop In [294]:% timeit deserialized = yaml.load (serializado) 1 bucles, lo mejor de 3: 2.03 s por bucle In [295]: len (serializado) Out [295]: 200091

De acuerdo. YAML es muy, muy lento. Pero, tenga en cuenta algo interesante: el tamaño serializado es de solo 200,091 bytes. Mucho mejor que tanto Pickle y JSON. Miremos dentro muy rápido:

En [300]: imprimir en serie [: 211] - a: & id001 booleano: verdadero int_list: [1, 2, 3] ninguno: número nulo: 3.44 texto: cadena cuando: 2016-03-13 00:11:44 - a : * id001 when: 2016-03-13 00:11:44 - a: * id001 when: 2016-03-13 00:11:44

YAML está siendo muy inteligente aquí. Identificó que todos los 5,000 dicts comparten el mismo valor para la tecla 'a', por lo que lo almacena solo una vez y lo hace referencia usando * id001 para todos los objetos.

Seguridad

La seguridad es a menudo una preocupación crítica. Pickle y YAML, en virtud de la construcción de objetos de Python, son vulnerables a los ataques de ejecución de código. Un archivo con formato inteligente puede contener código arbitrario que será ejecutado por Pickle o YAML. No hay que alarmarse. Esto es por diseño y está documentado en la documentación de Pickle:

Advertencia: el módulo pickle no está diseñado para ser seguro contra datos erróneos o creados de manera malintencionada. Nunca descomprima los datos recibidos de una fuente no confiable o no autenticada.

Así como en la documentación de YAML:

Advertencia: ¡No es seguro llamar a yaml.load con ningún dato recibido de una fuente no confiable! yaml.load es tan poderoso como pickle.load y, por lo tanto, puede llamar a cualquier función de Python.

Solo debe comprender que no debe cargar datos serializados recibidos de fuentes no confiables usando Pickle o YAML. JSON está bien, pero nuevamente si tiene codificadores / decodificadores personalizados de los que puede estar expuesto, también.

El módulo yaml proporciona la yaml.safe_load () Función que cargará solo objetos simples, pero luego perderá mucho poder de YAML y tal vez opte por usar JSON..

Otros formatos

Hay muchos otros formatos de serialización disponibles. Éstos son algunos de ellos.

Protobuf

Protobuf, o protocolo buffers, es el formato de intercambio de datos de Google. Se implementa en C ++ pero tiene enlaces Python. Tiene un esquema sofisticado y paquetes de datos de manera eficiente. Muy potente, pero no muy fácil de usar..

Paquete de mensajes

MessagePack es otro formato de serialización popular. También es binario y eficiente, pero a diferencia de Protobuf, no requiere un esquema. Tiene un sistema de tipos similar a JSON, pero un poco más rico. Las claves pueden ser de cualquier tipo, y no solo se admiten cadenas y cadenas que no son UTF8.

CBOR

CBOR significa Representación concisa de objetos binarios. Una vez más, es compatible con el modelo de datos JSON. CBOR no es tan conocido como Protobuf o MessagePack, pero es interesante por dos razones: 

  1. Es un estándar oficial de internet: RFC 7049.
  2. Fue diseñado específicamente para el Internet de las cosas (IoT)..

Como escoger?

Esta es la gran pregunta. Con tantas opciones, ¿cómo eliges? Consideremos los diversos factores que deben tenerse en cuenta:

  1. Si el formato serializado es legible por humanos y / o editable por humanos?
  2. ¿Se va a recibir contenido serializado de fuentes no confiables??
  3. ¿La serialización / deserialización es un cuello de botella en el rendimiento??
  4. ¿Es necesario intercambiar datos serializados con entornos que no sean de Python??

Lo haré muy fácil para usted y cubriré varios escenarios comunes y el formato que recomiendo para cada uno:

Guardado automático del estado local de un programa de Python

Use pickle (cPickle) aquí con la HIGHEST_PROTOCOL. Es rápido, eficiente y puede almacenar y cargar la mayoría de los objetos de Python sin ningún código especial. También se puede utilizar como caché local persistente..

Archivos de configuración

Definitivamente YAML. Nada supera su simplicidad para cualquier cosa que los humanos necesiten leer o editar. Es utilizado con éxito por Ansible y muchos otros proyectos. En algunas situaciones, es posible que prefiera utilizar módulos de Python directos como archivos de configuración. Esta puede ser la elección correcta, pero no es una serialización, y es realmente parte del programa y no un archivo de configuración separado.

APIs web

JSON es el claro ganador aquí. En estos días, las API web se consumen con mayor frecuencia por las aplicaciones web de JavaScript que hablan JSON de forma nativa. Algunas API web pueden devolver otros formatos (por ejemplo, csv para conjuntos de resultados tabulares densos), pero yo diría que puede empaquetar datos de csv en JSON con una sobrecarga mínima (no es necesario repetir cada fila como un objeto con todos los nombres de columna). 

Comunicación a gran escala de alto volumen / baja latencia

Utilice uno de los protocolos binarios: Protobuf (si necesita un esquema), MessagePack o CBOR. Ejecute sus propias pruebas para verificar el rendimiento y el poder representativo de cada opción.

Conclusión

La serialización y deserialización de los objetos de Python es un aspecto importante de los sistemas distribuidos. No puedes enviar objetos de Python directamente a través del cable. A menudo necesita interoperar con otros sistemas implementados en otros idiomas y, a veces, solo desea almacenar el estado de su programa en un almacenamiento persistente. 

Python viene con varios esquemas de serialización en su biblioteca estándar, y muchos más están disponibles como módulos de terceros. Ser consciente de todas las opciones y las ventajas y desventajas de cada una le permitirá elegir el mejor método para su situación.