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.
from jyquickhelper import add_notebook_menu
add_notebook_menu()
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.
import math
from io import StringIO
st = StringIO()
st.write("pi=")
st.write(str(math.pi))
st.write(";")
value = st.getvalue()
print(value)
pi=3.141592653589793;
st = StringIO("pi=3.141592653589793;")
while text := st.read(1):
print(text)
p i = 3 . 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 ;
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]
('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')
%timeit f1(data)
207 µs ± 25.6 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
%timeit f2(data)
365 µs ± 50.1 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
Il vaut mieux faire varier la longueur de la liste data
avant de répondre.
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.
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
{'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
.
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] + "...")
[ {"type":"lexeme","id":"L4","lemmas":{"en":{"language":"en","value":"windsurf"}},"lexicalCategory":"Q24905","language":"Q1860","claims":{"P5238":[{"m...
Modifier les données et les écrire de nouveau sur disque.
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)
[ {"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"},
from dict2xml import dict2xml
print(dict2xml(data))
<cartoons> <durée>5</durée> <title>Les Aventures célèbres de Monsieur Magoo</title> </cartoons> <cartoons> <durée>10</durée> <title>Quoi de neuf Mr. Magoo ?</title> </cartoons> <creator>Millard Kaufman</creator> <creator>John Hubley</creator> <naissance>1949</naissance> <nom>magoo</nom>
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.
On pourra utiliser les données json récupérées ci-dessus.
Même exercice en sens inverse.
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.