Sérialisation et désérialisation d'objets Python Partie 2

C’est la deuxième partie d’un tutoriel sur la sérialisation et la désérialisation des objets Python. Dans la première partie, vous avez appris les bases puis vous avez plongé dans les rouages ​​de Pickle et JSON. 

Dans cette partie, vous explorerez YAML (assurez-vous d'avoir l'exemple en cours de la première partie), discutez des considérations de performances et de sécurité, consultez un examen des formats de sérialisation supplémentaires et, enfin, apprenez à choisir le bon schéma..

YAML

YAML est mon format préféré. C'est un format de sérialisation de données convivial. Contrairement à Pickle et JSON, il ne fait pas partie de la bibliothèque standard Python, vous devez donc l'installer:

pip installer yaml

Le module yaml a seulement charge() et déverser() les fonctions. Par défaut, ils travaillent avec des chaînes comme charges() et décharges (), mais peut prendre un deuxième argument, qui est un flux ouvert, puis peut vider / charger dans / depuis des fichiers.

import yaml print yaml.dump (simple) booléen: vrai int_list: [1, 2, 3] aucun: null nombre: 3.44 texte: chaîne

Notez à quel point YAML est lisible par rapport à Pickle ou même JSON. Et maintenant, la partie la plus cool de YAML: elle comprend les objets Python! Pas besoin d'encodeurs et de décodeurs personnalisés. Voici la sérialisation / désérialisation complexe à l'aide de YAML:

> serialized = yaml.dump (complex)> print sérialisé a: !! python / object: __ main __. Un simple: boolean: true int_list: [1, 2, 3] none: nombre null: 3.44 texte: chaîne quand: 2016- 03-07 00:00:00> deserialized = yaml.load (serialized)> deserialized == complex True

Comme vous pouvez le constater, YAML a sa propre notation pour baliser les objets Python. La sortie est toujours très lisible par l'homme. L'objet datetime ne nécessite aucun marquage particulier, car YAML prend en charge de manière inhérente les objets datetime.. 

Performance

Avant de commencer à penser à la performance, vous devez vous demander si la performance est une préoccupation. Si vous sérialisez / désérialisez une petite quantité de données relativement peu fréquemment (par exemple, en lisant un fichier de configuration au début d'un programme), les performances ne sont pas vraiment un problème et vous pouvez passer à autre chose..

Mais, en supposant que vous profiliez votre système et découvriez que la sérialisation et / ou la désérialisation posent des problèmes de performances, voici les problèmes à résoudre.

Les performances sont deux aspects: la rapidité de la sérialisation / désérialisation et la taille de la représentation sérialisée.?

Pour tester les performances des différents formats de sérialisation, je vais créer une structure de données de grande taille et la sérialiser / désérialiser à l'aide de Pickle, YAML et JSON. le Big Data la liste contient 5 000 objets complexes.

big_data = [dict (a = simple, quand = datetime.now (). replace (microseconde = 0)) pour i dans l'intervalle (5000)]

Cornichon

Je vais utiliser IPython ici pour sa pratique % timeit fonction magique qui mesure les temps d'exécution.

importer cPickle en tant que pickle In [190]:% timeit serialized = pickle.dumps (big_data) 10 boucles, le meilleur de 3: 51 ms par boucle In [191]:% timeit deserialized = pickle.loads (sérialisé) 10 boucles, le meilleur des 3: 24,2 ms par boucle In [192]: désérialisé == big_data Out [192]: True In [193]: len (sérialisé) Out [193]: 747328

Le pickle par défaut prend 83,1 millisecondes pour se sérialiser et 29,2 millisecondes pour se désérialiser, et la taille sérialisée est de 747 328 octets..

Essayons avec le protocole le plus élevé.

Dans [195]:% timeit serialized = pickle.dumps (big_data, protocole = pickle.HIGHEST_PROTOCOL) 10 boucles, meilleur de 3: 21,2 ms par boucle. Dans [196]:% timeit deserialized = pickle.loads (sérialisé) 10 boucles, meilleur de 3: 25,2 ms par boucle In [197]: len (sérialisé) Out [197]: 394350

Des résultats intéressants. Le temps de sérialisation a été réduit à 21,2 millisecondes, mais le temps de désérialisation a augmenté légèrement pour atteindre 25,2 millisecondes. La taille sérialisée a été réduite de manière significative à 394 350 octets (52%).

JSON

Dans [253]% timeit serialized = json.dumps (big_data, cls = CustomEncoder) 10 boucles, meilleur de 3: 34,7 ms par boucle Dans [253]% timeit deserialized = json.loads (sérialisé, object_hook = decode_object) 10 boucles, meilleur de 3: 148 ms par boucle In [255]: len (sérialisé) Out [255]: 730000

D'accord. Les performances semblent être un peu moins bonnes que Pickle pour l’encodage, mais bien pire pour le décodage: 6 fois plus lentes. Que se passe-t-il? Ceci est un artefact de la objet_hook fonction devant être exécutée pour chaque dictionnaire pour vérifier s’il doit être converti en objet. Courir sans le crochet d'objet est beaucoup plus rapide.

% timeit désérialisé = json.loads (sérialisé) 10 boucles, meilleur de 3: 36,2 ms par boucle

La leçon à tirer est que, lors de la sérialisation et de la désérialisation en JSON, tenez compte de tout encodage personnalisé, car il peut avoir un impact majeur sur les performances globales..

YAML

In [293]:% timeit serialized = yaml.dump (big_data) 1 boucles, meilleur de 3: 1,22 s par boucle In [294]:% timeit désérialisé = yaml.load (sérialisé) 1 boucles, meilleur de 3: 2.03 s par boucle In [295]: len (sérialisé) Out [295]: 200091

D'accord. YAML est vraiment très lent. Mais notez quelque chose d'intéressant: la taille sérialisée n'est que de 200 091 octets. Bien mieux que Pickle et JSON. Regardons à l'intérieur très vite:

In [300]: print serialized [: 211] - a: & id001 boolean: true int_list: [1, 2, 3] none: nombre nul: 3.44 texte: chaîne de caractères: 2016-03-13 00:11:44 - a : * id001 quand: 2016-03-13 00:11:44 - a: * id001 quand: 2016-03-13 00:11:44

YAML est très intelligent ici. Il a identifié que tous les 5000 dict partagent la même valeur pour la clé 'a', il ne la stocke donc qu'une fois et la référence en utilisant * id001 pour tous les objets.

Sécurité

La sécurité est une préoccupation souvent critique. Pickle et YAML, en vertu de la construction d'objets Python, sont vulnérables aux attaques par exécution de code. Un fichier intelligemment formaté peut contenir du code arbitraire qui sera exécuté par Pickle ou YAML. Il n'y a pas besoin d'être alarmé. Ceci est voulu et est documenté dans la documentation de Pickle:

Avertissement: le module pickle n'est pas conçu pour être protégé contre les données erronées ou mal construites. Ne jamais supprimer les données reçues d'une source non approuvée ou non authentifiée.

Ainsi que dans la documentation de YAML:

Avertissement: Il n’est pas prudent d’appeler yaml.load avec des données reçues d’une source non fiable! yaml.load est aussi puissant que pickle.load et peut donc appeler n'importe quelle fonction Python.

Vous devez simplement comprendre que vous ne devez pas charger de données sérialisées provenant de sources non fiables à l'aide de Pickle ou de YAML. JSON est OK, mais encore une fois si vous avez des encodeurs / décodeurs personnalisés que vous pourriez être exposés, aussi.

Le module yaml fournit le yaml.safe_load () fonction qui ne charge que des objets simples, mais vous perdez alors beaucoup de puissance de YAML et peut-être optez pour l'utilisation de JSON.

Autres formats

Il existe de nombreux autres formats de sérialisation disponibles. En voici quelques uns.

Protobouf

Protobuf, ou tampon de protocole, est le format d'échange de données de Google. Il est implémenté en C ++ mais possède des liaisons Python. Il a un schéma sophistiqué et compresse les données efficacement. Très puissant, mais pas très facile à utiliser.

MessagePack

MessagePack est un autre format de sérialisation populaire. Il est également binaire et efficace, mais contrairement à Protobuf, il n’exige pas de schéma. Il a un système de type similaire à JSON, mais un peu plus riche. Les clés peuvent être de n'importe quel type, et pas seulement les chaînes et les chaînes non UTF8 sont prises en charge..

CBOR

CBOR est synonyme de représentation concise d'objets binaires. Là encore, il prend en charge le modèle de données JSON. CBOR n'est pas aussi connu que Protobuf ou MessagePack, mais il est intéressant pour deux raisons: 

  1. C'est une norme Internet officielle: RFC 7049.
  2. Il a été conçu spécifiquement pour l'Internet des objets (IoT).

Comment choisir?

C'est la grande question. Avec autant d'options, comment choisissez-vous? Considérons les différents facteurs à prendre en compte:

  1. Le format sérialisé doit-il être lisible et / ou éditable par l'homme??
  2. Le contenu sérialisé va-t-il être reçu de sources non fiables??
  3. La sérialisation / désérialisation est-elle un goulot d'étranglement??
  4. Les données sérialisées doivent-elles être échangées avec des environnements non Python??

Je vous simplifierai la tâche en vous proposant plusieurs scénarios courants et le format que je recommande pour chacun d'entre eux:

Enregistrement automatique de l'état local d'un programme Python

Utilisez pickle (cPickle) ici avec le HIGHEST_PROTOCOL. C'est rapide, efficace et peut stocker et charger la plupart des objets Python sans code spécial. Il peut également être utilisé comme cache persistant local.

Fichiers de configuration

Certainement YAML. Rien ne vaut sa simplicité pour tout ce que les humains ont besoin de lire ou d’éditer. Il est utilisé avec succès par Ansible et de nombreux autres projets. Dans certaines situations, vous préférerez peut-être utiliser des modules Python simples comme fichiers de configuration. C'est peut-être le bon choix, mais ce n'est pas une sérialisation, mais cela fait vraiment partie du programme et non un fichier de configuration séparé..

API Web

JSON est le gagnant clair ici. De nos jours, les API Web sont le plus souvent utilisées par les applications Web JavaScript qui parlent JSON de manière native. Certaines API Web peuvent renvoyer d'autres formats (par exemple, csv pour des ensembles de résultats tabulaires denses), mais je dirais que vous pouvez conditionner les données csv en JSON avec une surcharge minimale (inutile de répéter chaque ligne en tant qu'objet avec tous les noms de colonne).. 

Communication à grande échelle à volume élevé / faible latence

Utilisez l’un des protocoles binaires suivants: Protobuf (si vous avez besoin d’un schéma), MessagePack ou CBOR. Exécutez vos propres tests pour vérifier les performances et la puissance représentative de chaque option..

Conclusion

La sérialisation et la désérialisation des objets Python constituent un aspect important des systèmes distribués. Vous ne pouvez pas envoyer d'objets Python directement sur le réseau. Vous devez souvent interagir avec d'autres systèmes implémentés dans d'autres langues et parfois vous souhaitez simplement stocker l'état de votre programme dans un stockage persistant.. 

Python est livré avec plusieurs schémas de sérialisation dans sa bibliothèque standard, et de nombreux autres sont disponibles en tant que modules tiers. Être conscient de toutes les options et des avantages et inconvénients de chacun vous permettra de choisir la méthode la mieux adaptée à votre situation..