Site original : Sam & Max: Python, Django, Git et du cul
Depuis Python 3.3 existe un nouvel outil pour travailler avec les dicos et j’étais complètement passé à côté : ChainMap.
Il permet de créer un objet qui se comporte comme un dict, mais qui va chercher dans plusieurs dictionnaires.
Un exemple sera plus clair.
Imaginez que vous ayez un système de configuration avec des valeurs par défaut :
default_config = {'DEBUG': False, 'HOST': 'localhost', 'PORT': 8080}
Puis votre utilisateur peut fournir un fichier de configuration settings.py
qui contient :
DEBUG = True PORT = 8000
Et avec un peu de parsing, vous le récupérez sous forme de dico :
import settings user_config = {k: v for k, v in vars(settings).items() if k.isupper()} ## {'DEBUG': True, 'PORT': 8000}
Puis l’utilisateur peut passer la config via la ligne de commande, et une fois il fait :
--host 0.0.0.0
Et vous récupérez la config :
cmd_config = {"HOST": "0.0.0.0"}
Maintenant il faut prendre tout ça en compte. La ligne de commande écrase le fichier de config qui écrase les valeurs par défaut :
conf = {} conf.update(default_config) conf.update(user_config) conf.update(cmd_config) print(conf) # configuration finale ## {'DEBUG': True, 'HOST': '0.0.0.0', 'PORT': 8000}
Ça va marcher, mais ça a plusieurs défauts :
conf
, impossible de savoir quelle était sa valeur initiale.user_config
, il faut tout refusionner. Mais si vous avez modifié conf
entre temps, comment vous assurer que vous n’allez pas écraser ces modifications ?ChainMap résout ce problème en cherchant une clé dans une liste de dicos sous-jacent, mais en appliquant les modifications uniquement sur le premier dico.
>>> from collections import ChainMap >>> conf = ChainMap({}, # <- ce mapping sera le seul modifié # les clés seront cherchées dans cet ordre : cmd_config, user_config, default_config) >>> conf['HOST'] >>> '0.0.0.0' >>> conf['DEBUG'] >>> True >>> conf['PORT'] >>> 8000
Les dicos sont ici stockés par référence, ça ne prend pas de mémoire en plus, et si on modifie un dico :
user_config['DEBUG'] = False
Alors c’est reflété par ChainMap
:
>>> conf['DEBUG'] False
Si on fait une modification, seul le dico le plus haut dans la chaine (ici notre dico vide), est modifié :
>>> conf["PORT"] = 7777 >>> conf >>> ChainMap({'PORT': 7777}, {'HOST': '0.0.0.0'}, {'DEBUG': False, 'PORT': 8000}, {'DEBUG': False, 'HOST': 'localhost', 'PORT': 8080})
Et si on a besoin d’un contexte temporaire, on peut créer un enfant :
>>> sub_conf = conf.new_child() >>> sub_conf ChainMap({}, {'PORT': 7777}, {'HOST': '0.0.0.0'}, {'DEBUG': False, 'PORT': 8000}, {'DEBUG': False, 'HOST': 'localhost', 'PORT': 8080})
Cela crée un nouveau ChainMap, avec un dico vide en haut de la chaîne, qui permet donc de travailler temporairement avec de nouvelles valeurs, sans toucher au ChainMap d’origine.