Introduction
Un simple fichier JSON :
resources.json
{
"id": 307
,"date": "April 14, 2020"
,"thumbnail": "python"
,"langurls":[
{"lang":"fr" , "file":"python-parsing-des-arguments-argparse-et-getopt.html"}
,{"lang":"en", "file":"python-parsing-arguments-packages-argparse-getopt.html"}
]
,"sitemap" : { "title" : "Conception - Python", "url" : "/conception/python" }
}
Avec Javascript, après avoir récupéré le fichier JSON, les propriétés ou attributs sont habituellement directement manipulées avec la notation point (dot notation) :
props = {}; load_prop = async function() { const res = await fetch("https://www.sqlpac.ger/referentiel/docs/resources.json"); if (res.status==200) response = await res.json(); props = response; }; show_prop = function() { console.log(props.date); }; load_prop().then(show_prop);
console> April 14, 2020
En basculant en Python, mauvaises nouvelles : la notation dot n’est pas directement possible avec le dictionnaire résultant.
import json def main(): with open('resources.json','rb') as f: props = json.load(f) print(props.date) if (__name__=="__main__"): main()
print(props.date) AttributeError: 'dict' object has no attribute 'date'
On doit utiliser la syntaxe usuelle : props["attribute1"]["attribute2"]…
import json def main(): with open('resources.json','rb') as f: props = json.load(f) print(props["date"]) if (__name__=="__main__"): main()
April 14, 2020
Python n’est pas Javascript. Habitudes, habitudes, difficile de les éradiquer. Cet exemple traite du chargement de données JSON, le même contexte se produit lors du chargement de données YAML…
Comment utiliser la notation dot avec Python sur un dictionnaire ? Cela peut être réalisé nativement mais quand les dictionnaires sont imbriqués, cela devient fastidieux sans avoir à développer sa propre bibliothèque. Des packages maintenus de la communauté Python existent pour faire le job : Prodict, python-box.
Les méthodes natives
Remplir le dictionnaire d’une classe
Le dictionnaire d’une classe est alimenté par les données :
import json class clsprops: def __init__(self, data): self.__dict__ = data def main(): with open('resources.json','rb') as f: props = clsprops(json.load(f)) print(props.date) if (__name__=="__main__"): main()
April 14, 2020
Mais qu’en est-il des attributs des dictionnaires imbriqués ( { … ,"sitemap": { "url":"…", "title":"…" } … }
) ?
Prévisible, les attributs des dictionnaires imbriqués ne peuvent pas être utilisés avec la notation dot.
def main(): with open('resources.json','rb') as f: props = clsprops(json.load(f)) print(props.sitemap.url)
print(props.sitemap.url) AttributeError: 'dict' object has no attribute 'url'
props.sitemap["url"]
doit être utilisé.
En utilisant SimpleNamespace
Un code plus élégant en utilisant SimpleNamespace
sans avoir à déclarer une classe personnalisée :
import json from types import SimpleNamespace def main(): with open('resources.json','rb') as f: props = SimpleNamespace(** json.load(f)) print(props.date) if (__name__=="__main__"): main()
April 14, 2020
Même problème que lors de l’utilisation d’une classe personnalisée, les attributs des dictionnaires imbriqués ne sont pas accessibles avec la notation dot :
def main(): with open('resources.json','rb') as f: props = SimpleNamespace(** json.load(f)) print(props.sitemap.url)
AttributeError: 'dict' object has no attribute 'url'
Les packages "Dot notation dictionary"
Heureusement, il y a tellement de packages dans la communauté Python qu’il doit bien y en avoir quelques uns qui couvrent ce besoin. Le plus difficile est de trouver ceux qui offrent les meilleures fonctionnalités, avec le moins de dépendances et par dessus tout un package maintenu (regarder les dates des dernières releases, révélateur sur la pérennité et le support du package).
2 packages sélectionnés :
prodict
L’installation de Prodict n’a pas de dépendances :
pip3 search prodict
prodict (0.8.3) - Prodict = Pro Dictionary with IDE friendly(auto code completion), dot-accessible attributes and more.
pip3 install product
Successfully installed prodict-0.8.3
La mise en œuvre et l’utilisation de la notation dot est alors très facile,
les clés non définies retournent None
au lieu de l’erreur par défaut dict AttributeError
et des clés peuvent être ajoutées dynamiquement :
import json from prodict import Prodict def main(): with open('resources.json','rb') as f: props = Prodict.from_dict(json.load(f)) print(props.sitemap.url) if (props.sitemap.url2==None): props.sitemep.url2="/conception/python-3.8" print(props.sitemep.url2) if (__name__=="__main__"): main()
/conception/python /conception/python-3.8
Juste un inconvénient, quand les dictionnaires imbriqués sont définis dans une liste et uniquement dans ce cas :
"langurls":[
{"lang":"fr" , "file":"python-parsing-des-arguments-argparse-et-getopt.html"}
,{"lang":"en", "file":"python-parsing-arguments-packages-argparse-getopt.html"}
]
un nouvel objet Prodict est nécessaire pour utiliser la notation dot, sinon l’erreur AttributeError
est levée :
import json from prodict import Prodict def main(): with open('resources.json','rb') as f: props = Prodict.from_dict(json.load(f)) # print(props.langurls[0].lang) Not possible with Prodict, Attribute Error raised print(Prodict.from_dict(props.langurls[0]).lang) if (__name__=="__main__"): main()
fr
L’objet Prodict est un dictionnaire, aussi il peut être utilisé comme un dictionnaire et comparé à des dictionnaires classiques, aucune translation nécessaire :
with open('results.json','w') as r:
json.dump(props, r, indent=4, ensure_ascii=False)
…,
"sitemap": {
"title": "Conception - Python",
"url": "/conception/python",
"url2": "/conception/python-3.8"
}
python-box
Python-box a des dépendances (les packages des parsers ruamel.yaml
et toml
) :
pip3 search python-box
python-box (4.2.2) - Advanced Python dictionaries with dot notation access
pip3 install python-box
Successfully installed python-box-4.2.2 ruamel.yaml-0.16.10 ruamel.yaml.clib-0.2.0 toml-0.10.0
Pour utiliser python-box
import json from box import Box def main(): with open('resources.json','rb') as f: props = Box(json.load(f)) print(props.sitemap.url) if (__name__=="__main__"): main()
/conception/python
À propos de l’inconvénient noté avec Prodict
, les dictionnaires définis dans une liste sont bien gérés par python-box
:
import json from box import Box def main(): with open('resources.json','rb') as f: props = Box(json.load(f)) print(props.sitemap.url) print(props.langurls[0].lang) if (__name__=="__main__"): main()
/conception/python fr
Les objets Box peuvent être comparés aux dictionnaires natifs. La méthode to_dict
traduit un objet Box en dictionnaire natif,
mais les quelques tests réalisés pour des actions usuelles (json.dump
par exemple) n’ont pas nécessité d’utiliser la méthode
to_dict
.
L’inconvénient :
- L’interrogation de clés non définis lève une erreur(
BoxKeyError
)
Conclusion
De l’opinion de l’auteur de cet article, le package Prodict est le plus adapté : pas de dépendances, très léger en code et surtout, on peut gérer les clés non existantes comme on le ferait en Javascript sans qu’une exception soit levée, c’est exactement la fonctionnalité supplémentaire que l’on cherchait en plus de la notation dot :
if (props.sitemap.url2 == undefined) { // Javascript code }
if (props.sitemap.url2 == None) :
# Python code
Dans le cas où des dictionnaire sont définis dans une liste, des contournements sont possibles.