.. _2022serialisationrst: ==================== Tech - Sérialisation ==================== .. only:: html **Links:** :download:`notebook <2022_serialisation.ipynb>`, :downloadlink:`html <2022_serialisation2html.html>`, :download:`python <2022_serialisation.py>`, :downloadlink:`slides <2022_serialisation.slides.html>`, :githublink:`GitHub|_doc/notebooks/td1a_home/2022_serialisation.ipynb|*` La sérialisation répond à un problème simple : comment échanger des données complexes autres que des tableaux ? Si l’énoncé est simple, la solution ne l’est pas toujours. Il est assez facile d’échanger des données qui se présentent sous la forme d’un tableau, d’un texte, d’un nombre mais comment échanger un assemblage de données hétérogènes ? La **sérialisation** désigne un méanisme qui permet de permet de représenter un assemblage de données en un seul tableau de caractères. La **désérialisation** désigne le mécanisme inverse qui consiste à reconstruire les données initiales à partir de ce tableau de caractères. .. code:: ipython3 from jyquickhelper import add_notebook_menu add_notebook_menu() .. contents:: :local: Notion de stream ou flux ------------------------ Un stream en informatique définit une façon de parcourir une séquence d’octets. Un fichier est un stream : on écrit les octets ou caractères les uns après les autres, chaque nouveau caractère est ajouté à la fin. Lors de la lecture, on procède de même en lisant les caractères du début à la fin. Dans un stream, on ne revient jamais en arrière, on lit toujours le caractère suivant. Les streams sont optimisés pour ce type de lecture et d’écriture, ils sont très lents lorsqu’il s’agit d’aller lire ou écrire des caractères de façon non séquentielle. Pour faire des calculs mathématiques, il faut pouvoir accéder à tout moment à n’importe quel élément de la matrice. L’utilisation d’un *stream* est contre-indiquée. En revanche, ils sont très adaptés à la lecture et l’écriture de fichiers. Ils sont également utilisés pour communiquer des données, lorsqu’un ordinateur envoie des données à un autre ordinateur. .. code:: ipython3 import math from io import StringIO st = StringIO() st.write("pi=") st.write(str(math.pi)) st.write(";") value = st.getvalue() print(value) .. parsed-literal:: pi=3.141592653589793; .. code:: ipython3 st = StringIO("pi=3.141592653589793;") while text := st.read(1): print(text) .. parsed-literal:: p i = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 ; .. code:: ipython3 def f1(text): st = StringIO() for t in text: st.write(t) st.write(";") value = st.getvalue() return value def f2(text): s = "" for t in text: s += t + ";" return s data = ["petit", "essai", "de", "comparaison"] * 300 f1(data)[:100], f2(data)[:100] .. parsed-literal:: ('petit;essai;de;comparaison;petit;essai;de;comparaison;petit;essai;de;comparaison;petit;essai;de;comp', 'petit;essai;de;comparaison;petit;essai;de;comparaison;petit;essai;de;comparaison;petit;essai;de;comp') .. code:: ipython3 %timeit f1(data) .. parsed-literal:: 207 µs ± 25.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) .. code:: ipython3 %timeit f2(data) .. parsed-literal:: 365 µs ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each) Q1: quelle est la fonction la plus rapide ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Il vaut mieux faire varier la longueur de la liste ``data`` avant de répondre. Format JSON ----------- Le format `JSON `__ est le format le plus répandu sur Internet. C’est un assemblage récursif de listes et de dictionnaires. Chaque conteneur peut contenir des listes, des dictionnaires, des nombres, des chaînes de caractères. Il est possible de télécharger tout Wikipédia au format JSON : `Wikidata:Téléchargement de la base de données `__. .. code:: ipython3 data = { "nom": "magoo", "naissance": 1949, "creator": ["Millard Kaufman", "John Hubley"], "cartoons": [ {"title": "Les Aventures célèbres de Monsieur Magoo", "durée": 5}, {"title": "Quoi de neuf Mr. Magoo ?", "durée": 10} ] } data .. parsed-literal:: {'nom': 'magoo', 'naissance': 1949, 'creator': ['Millard Kaufman', 'John Hubley'], 'cartoons': [{'title': 'Les Aventures célèbres de Monsieur Magoo', 'durée': 5}, {'title': 'Quoi de neuf Mr. Magoo ?', 'durée': 10}]} Le langage python propose une librairie standard `json `__ pour manipuler les informations. Et comme c’est d’un usage fréquent, il existe d’autres options plus rapides `ujson `__, `simplejson `__, `ijson `__, … La page `Index of /wikidatawiki/entities/ `__ contient des fichiers json issues de wikipedia. Le fichier `latest-lexemes-sample.json `__ contient les premières lignes de ``latest-lexemes.json.bz2``. Q1. lire du json ~~~~~~~~~~~~~~~~ Télécharger et lire ce `fichier `__ avec la libraire `json `__. .. code:: ipython3 from urllib.request import urlopen url = "https://raw.githubusercontent.com/sdpython/ensae_teaching_cs/master/_doc/notebooks/td1a_home/latest-lexemes-sample.json" with urlopen(url) as f: text = f.read().decode("utf-8") print(text[:150] + "...") .. parsed-literal:: [ {"type":"lexeme","id":"L4","lemmas":{"en":{"language":"en","value":"windsurf"}},"lexicalCategory":"Q24905","language":"Q1860","claims":{"P5238":[{"m... Q2: écrire du json ~~~~~~~~~~~~~~~~~~ Modifier les données et les écrire de nouveau sur disque. Q3: gros json ~~~~~~~~~~~~~ Le dump de la version anglaise de wikipedia fait plus de 100 Go (en version compressée). Il tient sur disque mais pas en mémoire. Comment faire pour le lire malgré tout ? Quelques lignes pour vous données des idées… Les plus courageux utiliseront la librairie `ijson `__ ou `orjson `__. .. code:: ipython3 with open("dummy.json", "w", encoding="utf-8") as f: f.write(text) with open("dummy.json", "r", encoding="utf-8") as f: for i, line in enumerate(f): if i > 2: break print(line) .. parsed-literal:: [ {"type":"lexeme","id":"L4","lemmas":{"en":{"language":"en","value":"windsurf"}},"lexicalCategory":"Q24905","language":"Q1860","claims":{"P5238":[{"mainsnak":{"snaktype":"value","property":"P5238","datavalue":{"value":{"entity-type":"lexeme","numeric-id":3324,"id":"L3324"},"type":"wikibase-entityid"},"datatype":"wikibase-lexeme"},"type":"statement","qualifiers":{"P1545":[{"snaktype":"value","property":"P1545","hash":"2a1ced1dca90648ea7e306acbadd74fc81a10722","datavalue":{"value":"1","type":"string"},"datatype":"string"}]},"qualifiers-order":["P1545"],"id":"L4$faad30b0-421c-803a-c1fd-b9a99a0eb35d","rank":"normal"},{"mainsnak":{"snaktype":"value","property":"P5238","datavalue":{"value":{"entity-type":"lexeme","numeric-id":18537,"id":"L18537"},"type":"wikibase-entityid"},"datatype":"wikibase-lexeme"},"type":"statement","qualifiers":{"P1545":[{"snaktype":"value","property":"P1545","hash":"7241753c62a310cf84895620ea82250dcea65835","datavalue":{"value":"2","type":"string"},"datatype":"string"}]},"qualifiers-order":["P1545"],"id":"L4$d15285a1-4880-7a9b-bb1f-85403e1a785a","rank":"normal"}],"P5187":[{"mainsnak":{"snaktype":"value","property":"P5187","datavalue":{"value":{"text":"windsurf","language":"en"},"type":"monolingualtext"},"datatype":"monolingualtext"},"type":"statement","id":"L4$d4a63d17-43ea-749d-5860-21b90feb83f7","rank":"normal"}]},"forms":[{"id":"L4-F1","representations":{"en":{"language":"en","value":"windsurfing"}},"grammaticalFeatures":["Q10345583"],"claims":[]},{"id":"L4-F3","representations":{"en":{"language":"en","value":"windsurfs"}},"grammaticalFeatures":["Q110786","Q3910936","Q51929074"],"claims":[]},{"id":"L4-F4","representations":{"en":{"language":"en","value":"windsurfed"}},"grammaticalFeatures":["Q1392475"],"claims":[]},{"id":"L4-F5","representations":{"en":{"language":"en","value":"windsurfed"}},"grammaticalFeatures":["Q1230649"],"claims":[]},{"id":"L4-F6","representations":{"en":{"language":"en","value":"windsurf"}},"grammaticalFeatures":["Q3910936"],"claims":[]}],"senses":[{"id":"L4-S1","glosses":{"fr":{"language":"fr","value":"faire de la planche \u00e0 voile"},"ms":{"language":"ms","value":"meluncur angin"},"zh":{"language":"zh","value":"\u6ed1\u6d6a\u98a8\u5e06"},"zh-hant":{"language":"zh-hant","value":"\u6ed1\u6d6a\u98a8\u5e06"},"zh-tw":{"language":"zh-tw","value":"\u6ed1\u6d6a\u98a8\u5e06"},"nan":{"language":"nan","value":"h\u00e1i-\u00edng hong-ph\u00e2ng"},"th":{"language":"th","value":"\u0e40\u0e25\u0e48\u0e19\u0e27\u0e34\u0e19\u0e14\u0e4c\u0e40\u0e0b\u0e34\u0e23\u0e4c\u0e1f"},"tg":{"language":"tg","value":"\u0441\u0451\u0440\u0444\u0438\u043d\u0433\u0431\u043e\u0437\u0438\u0438 \u0448\u0430\u043c\u043e\u043b\u04e3"},"fi":{"language":"fi","value":"purjelautailla"}},"claims":{"P5137":[{"mainsnak":{"snaktype":"value","property":"P5137","datavalue":{"value":{"entity-type":"item","numeric-id":191051,"id":"Q191051"},"type":"wikibase-entityid"},"datatype":"wikibase-item"},"type":"statement","id":"L4-S1$13e5f498-4deb-ea41-4d60-02c852b88b4c","rank":"normal"}],"P5972":[{"mainsnak":{"snaktype":"value","property":"P5972","datavalue":{"value":{"entity-type":"sense","id":"L144039-S1"},"type":"wikibase-entityid"},"datatype":"wikibase-sense"},"type":"statement","id":"L4-S1$7218013F-B84B-40FA-B57B-BC1BA2239BB8","rank":"normal"}]}}],"pageid":54387040,"ns":146,"title":"Lexeme:L4","lastrevid":1710596079,"modified":"2022-08-22T19:28:34Z"}, {"type":"lexeme","id":"L314","lemmas":{"ca":{"language":"ca","value":"pi"}},"lexicalCategory":"Q1084","language":"Q7026","claims":{"P5185":[{"mainsnak":{"snaktype":"value","property":"P5185","datavalue":{"value":{"entity-type":"item","numeric-id":1775415,"id":"Q1775415"},"type":"wikibase-entityid"},"datatype":"wikibase-item"},"type":"statement","id":"L314$45650151-4ed8-025d-2442-e36ef22e6a2a","rank":"normal"}]},"forms":[{"id":"L314-F1","representations":{"ca":{"language":"ca","value":"pis"}},"grammaticalFeatures":["Q146786"],"claims":[]},{"id":"L314-F2","representations":{"ca":{"language":"ca","value":"pi"}},"grammaticalFeatures":["Q110786"],"claims":[]}],"senses":{},"pageid":54387050,"ns":146,"title":"Lexeme:L314","lastrevid":684359491,"modified":"2018-05-24T07:28:21Z"}, XML --- Le `XML `__ était utilisé avant le format json. Il permet de faire la même chose, sérialiser, mais est plus verbeux. Il a été abandonné car le résultat est plus long qu’avec le format json. .. code:: ipython3 from dict2xml import dict2xml print(dict2xml(data)) .. parsed-literal:: 5 Les Aventures célèbres de Monsieur Magoo 10 Quoi de neuf Mr. Magoo ? Millard Kaufman John Hubley 1949 magoo pickle ------ Le format `JSON `__ a un inconvénient majeur : il impose la conversion des données au format texte, en particulier les nombres. Chaque nombre doit être converti en chaînes de caractères et réciproquement. Pourquoi ne pas garder la représentation binaire des nombres tels qu’ils sont utilisés en mémoire ? C’est l’objectif du module `pickle `__. Comme il n’y pas de conversion au format texte et qu’il s’agit de recopier la mémoire sur disque en un seule, cette sérialisation s’applique à tout objet python. Elle n’est pas restreinte aux dictionnaires et aux listes. Q1: comparer le temps de sérialisation entre pickle et json ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On pourra utiliser les données json récupérées ci-dessus. Q2: comparer le temps de désérialisation entre pickle et json ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Même exercice en sens inverse. Peut-on tout sérialiser ? ~~~~~~~~~~~~~~~~~~~~~~~~~ La plupart des objets contenant des données peuvent être sérialisées, les listes, ldes dictionnaires, les matrices (numpy), les dataframes)… Il n’est pas possible de sérialiser les fonctions à moins d’utiliser des librairies comme `cloudpickle `__ ou `dill `__. La sérialisation fonctionne de façon implicite avec toutes les classes python à l’exception de celles définies en C++. Pour celles-ci, il faudra coder explicitement la sérialisation et la désérialisation. Pour cela il faut redéfinir les méthodes `getstate et_setstate `__. Il reste une contrainte majeure à cette sérialisation, elle dépend de la version du langage et de chaque extension. Sérialisation avec python 3.7 et désérialisation avec python 3.10 a peu de chance de fonctionner.