web-dev-qa-db-fra.com

À quoi servent les collections.ChainMap?

Dans Python 3.3 une classe ChainMap a été ajoutée au module collections :

Une classe ChainMap est fournie pour relier rapidement un certain nombre de mappages afin qu'ils puissent être traités comme une seule unité. C'est souvent beaucoup plus rapide que de créer un nouveau dictionnaire et d'exécuter plusieurs appels update ().

Exemple:

>>> from collections import ChainMap
>>> x = {'a': 1, 'b': 2}
>>> y = {'b': 10, 'c': 11}
>>> z = ChainMap(y, x)
>>> for k, v in z.items():
        print(k, v)
a 1
c 11
b 10

Il était motivé par ce problème et rendu public par celui-ci (aucun PEP n'a été créé).

Pour autant que je sache, c'est une alternative à avoir un dictionnaire supplémentaire et à le maintenir avec update() s.

Les questions sont:

  • Quels cas d'utilisation couvre ChainMap?
  • Existe-t-il des exemples réels de ChainMap?
  • Est-il utilisé dans des bibliothèques tierces qui sont passées à python3?

Question bonus: existe-t-il un moyen de l'utiliser sur Python2.x?


J'en ai entendu parler dans Transforming Code into Beautiful, Idiomatic Python Conférence PyCon de Raymond Hettinger et j'aimerais l'ajouter à ma boîte à outils, mais je ne sais pas quand l'utiliser.

59
alecxe

J'aime les exemples de @ b4hand, et en effet j'ai utilisé dans le passé des structures de type ChainMap (mais pas ChainMap lui-même) pour les deux objectifs qu'il mentionne: les remplacements de configuration multi-couches et l'émulation de pile/portée variable.

Je voudrais souligner deux autres motivations/avantages/différences de ChainMap, par rapport à l'utilisation d'une boucle de mise à jour de dict, ne stockant ainsi que la version "finale" ":

  1. Plus d'informations: car une structure ChainMap est "en couches", elle prend en charge la réponse à des questions telles que: ai-je la valeur "par défaut" ou une valeur remplacée? Quelle est la valeur d'origine ("par défaut")? À quel niveau la valeur a-t-elle été remplacée (emprunt à l'exemple de configuration de @ b4hand: user-config ou command-line-overrides)? En utilisant un dict simple, les informations nécessaires pour répondre à ces questions sont déjà perdues.

  2. Compromis de vitesse: supposons que vous ayez N couches et au plus M clés dans chacune, la construction d'un ChainMap prend O(N) et chaque recherche O(N) pire cas [*], tandis que la construction d'un dict utilisant une boucle de mise à jour prend O(NM) et chaque recherche O(1) . Cela signifie que si vous construisez souvent et n'effectuez que quelques recherches à chaque fois, ou si M est important, l'approche de construction paresseuse de ChainMap fonctionne en votre faveur.

[*] L'analyse en (2) suppose que dict-access est O(1), alors qu'en fait c'est O(1) en moyenne, et O(M) pire cas. Voir plus de détails ici .

59
shx2

Je pourrais voir utiliser ChainMap pour un objet de configuration où vous avez plusieurs étendues de configuration comme des options de ligne de commande, un fichier de configuration utilisateur et un fichier de configuration système. Étant donné que les recherches sont classées par ordre dans l'argument constructeur, vous pouvez remplacer les paramètres des étendues inférieures. Je n'ai pas personnellement utilisé ou vu ChainMap utilisé, mais ce n'est pas surprenant car c'est un ajout assez récent à la bibliothèque standard.

Il peut également être utile pour émuler des cadres de pile dans lesquels vous effectuez un push et un pop des liaisons de variables si vous essayez d'implémenter vous-même une étendue lexicale.

Les documentation de bibliothèque standard pour ChainMap donnent plusieurs exemples et liens vers des implémentations similaires dans des bibliothèques tierces. Plus précisément, il nomme Django classe Context et Enthought classe MultiContext .

36
b4hand

Je vais prendre une fissure à ceci:

Chainmap ressemble à une sorte d'abstraction très juste. C'est une bonne solution pour un type de problème très spécialisé. Je propose ce cas d'utilisation.

Si tu as:

  1. plusieurs mappages (par exemple, dict)
  2. une certaine duplication des clés dans ces mappages (la même clé peut apparaître dans plusieurs mappages, mais pas le cas où toutes les clés apparaissent dans tous les mappages)
  3. une application consommatrice qui souhaite accéder à la valeur d'une clé dans le mappage de "priorité la plus élevée" où il y a un classement total sur tous les mappages pour une clé donnée (c'est-à-dire que les mappages peuvent avoir une priorité égale, mais seulement s'il est connu que il n'y a pas de duplication de clé dans ces mappages) (Dans l'application Python, les packages peuvent vivre dans le même répertoire (même priorité) mais doivent avoir des noms différents, donc, par définition, les noms des symboles dans ce répertoire ne peuvent pas être des doublons.)
  4. l'application consommatrice n'a pas besoin de modifier la valeur d'une clé
  5. tandis qu'en même temps, les mappages doivent conserver leur identité indépendante et peuvent être modifiés de manière asynchrone par une force externe
  6. et les mappages sont suffisamment volumineux, assez chers pour accéder à, ou changent assez souvent entre les accès aux applications, que le coût du calcul de la projection (3) chaque fois que votre application en a besoin est un problème de performances important pour votre application ...

Ensuite, vous pouvez envisager d'utiliser un plan de chaîne pour créer une vue sur la collection de mappages.

Mais ce n'est là qu'une justification après coup. Les gars de Python ont eu un problème, ont trouvé une bonne solution dans le contexte de leur code, puis ont fait un travail supplémentaire pour résumer leur solution afin que nous puissions l'utiliser si nous le choisissons. Plus de puissance pour Mais c'est à vous de décider s'il convient à votre problème.

6
BobHy

Pour répondre imparfaitement à votre:

Question bonus: existe-t-il un moyen de l'utiliser sur Python2.x?

from ConfigParser import _Chainmap as ChainMap

Cependant gardez à l'esprit que ce n'est pas un vrai ChainMap, il hérite de DictMixin et ne définit que:

__init__(self, *maps)
__getitem__(self, key)
keys(self)

# And from DictMixin:
__iter__(self)
has_key(self, key)
__contains__(self, key)
iteritems(self)
iterkeys(self)
itervalues(self)
values(self)
items(self)
clear(self)
setdefault(self, key, default=None)
pop(self, key, *args)
popitem(self)
update(self, other=None, **kwargs)
get(self, key, default=None)
__repr__(self)
__cmp__(self, other)
__len__(self)

Sa mise en œuvre ne semble pas non plus particulièrement efficace.

5
A T