64 votes

Limiter la taille d'un dictionnaire python

J'aimerais travailler avec un dict en python, mais limiter le nombre de paires clé/valeur à X. En d'autres termes, si le dict stocke actuellement X paires clé/valeur et que j'effectue une insertion, je voudrais qu'une des paires existantes soit abandonnée. Ce serait bien si c'était la clé la moins récemment insérée/accédée, mais ce n'est pas complètement nécessaire.

Si cela existe dans la bibliothèque standard, faites-moi gagner du temps et indiquez-le !

1 votes

0 votes

Bonne trouvaille. j'aimerais bien garder ça dans les parages puisque je n'ai pas spécifiquement besoin de lru.

0 votes

Nick : Limiter la taille semble être une distinction suffisante pour être une question différente, mais oui, cette question est la moitié du problème.

60voto

Python 2.7 et 3.1 ont OrderedDict et il existe des implémentations purement Python pour les Python antérieurs.

class LimitedSizeDict(OrderedDict):
  def __init__(self, *args, **kwds):
    self.size_limit = kwds.pop("size_limit", None)
    OrderedDict.__init__(self, *args, **kwds)
    self._check_size_limit()

  def __setitem__(self, key, value):
    OrderedDict.__setitem__(self, key, value)
    self._check_size_limit()

  def _check_size_limit(self):
    if self.size_limit is not None:
      while len(self) > self.size_limit:
        self.popitem(last=False)

Il faudrait aussi surcharger d'autres méthodes qui peuvent insérer des éléments, comme update. L'utilisation principale de OrderedDict est de pouvoir contrôler facilement ce qui est inséré, sinon un dict normal fonctionnerait.

0 votes

@Mike : (self, size_limit=None, *args, **kwds) est erronée et les paramètres de type mot-clé uniquement ( (self, *args, size_limit=None, **kwds) ) ne sont pas dans la version actuelle de la 2.x. (Sont-ils envisagés pour la 2.7 ? En tout cas, ils ne sont pas dans la 2.6.) Ce code fonctionne avec à peu près toutes les versions que l'OP est susceptible d'utiliser, ce qui fait que taille_limite est effectivement un paramètre à mot-clé seulement, et maintient la même interface que les dicts.

1 votes

Avez-vous testé votre code sur Python 2.7 ? dict.pop nécessite au moins 1 argument. dict.popitem() fonctionne, mais il a supprimé l'élément récent.

1 votes

Aussi, setitem doit vérifier la limite de taille avant de mettre en place l'objet, sinon vous finirez par perdre l'objet même que vous mettez en place !

15voto

Alex Martelli Points 330805

Voici une solution simple, sans LRU, pour Python 2.6+ (dans les Python plus anciens, vous pouviez faire quelque chose de similaire avec UserDict.DictMixin mais dans les versions 2.6 et supérieures, ce n'est pas recommandé, et l'ABC de collections sont de toute façon préférables...) :

import collections

class MyDict(collections.MutableMapping):
  def __init__(self, maxlen, *a, **k):
    self.maxlen = maxlen
    self.d = dict(*a, **k)
    while len(self) > maxlen:
      self.popitem()
  def __iter__(self):
    return iter(self.d)
  def __len__(self):
    return len(self.d)
  def __getitem__(self, k):
    return self.d[k]
  def __delitem__(self, k):
    del self.d[k]
  def __setitem__(self, k, v):
    if k not in self and len(self) == self.maxlen:
      self.popitem()
    self.d[k] = v 

d = MyDict(5)
for i in range(10):
  d[i] = i
  print sorted(d)

Comme d'autres réponses l'ont mentionné, vous n'avez probablement pas envie de sous-classer dict - la délégation explicite à self.d est malheureusement très compliquée, mais elle permet d'atteindre les objectifs suivants garantie que toute autre méthode est correctement fournie par collections.MutableDict .

9voto

Raymond Hettinger Points 50330

Voici un cache LRU simple et efficace écrit avec un simple code Python qui fonctionne sur n'importe quelle version de Python 1.5.2 ou plus :

class LRU_Cache:

    def __init__(self, original_function, maxsize=1000):
        self.original_function = original_function
        self.maxsize = maxsize
        self.mapping = {}

        PREV, NEXT, KEY, VALUE = 0, 1, 2, 3         # link fields
        self.head = [None, None, None, None]        # oldest
        self.tail = [self.head, None, None, None]   # newest
        self.head[NEXT] = self.tail

    def __call__(self, *key):
        PREV, NEXT = 0, 1
        mapping, head, tail = self.mapping, self.head, self.tail

        link = mapping.get(key, head)
        if link is head:
            value = self.original_function(*key)
            if len(mapping) >= self.maxsize:
                old_prev, old_next, old_key, old_value = head[NEXT]
                head[NEXT] = old_next
                old_next[PREV] = head
                del mapping[old_key]
            last = tail[PREV]
            link = [last, tail, key, value]
            mapping[key] = last[NEXT] = tail[PREV] = link
        else:
            link_prev, link_next, key, value = link
            link_prev[NEXT] = link_next
            link_next[PREV] = link_prev
            last = tail[PREV]
            last[NEXT] = tail[PREV] = link
            link[PREV] = last
            link[NEXT] = tail
        return value

if __name__ == '__main__':
    p = LRU_Cache(pow, maxsize=3)
    for i in [1,2,3,4,5,3,1,5,1,1]:
        print(i, p(i, 2))

3 votes

Wow, vous écrivez à peu près LRU en si peu de temps pour différents cas d'utilisation, c'est toujours une joie de lire votre code Python, que ce soit dans le livre de recettes python (activestate), la librairie standard python, le blog, twitter, ou les conférences pycon, et maintenant sur stackoverflow aussi.

0 votes

Cette mise en œuvre semble non pythique. Pourquoi ne pas utiliser OrderedDict y MutableMapping pour faire ça...

4 votes

C'est parfaitement Pythonique - une classe simple avec une utilisation directe des dictionnaires, des listes, des affectations de base et du déballage. La logique est autonome et il n'y a pas de dépendances externes. Ce code est également très rapide et peut être facilement optimisé par PyPy. Un OrderedDict ajoute de l'espace supplémentaire (il utilise deux dicts en interne alors que celui-ci n'en utilise qu'un seul) et il effectue un travail inutile qui n'est pas nécessaire pour cette utilisation. MutableMapping ne fournit aucune capacité utile dans ce contexte.

2voto

Mike Graham Points 22480

Un dict n'a pas ce comportement. Vous pouvez créer votre propre classe qui fait cela, par exemple quelque chose comme

class MaxSizeDict(object):
    def __init__(self, max_size):
        self.max_size = max_size
        self.dict = {}
    def __setitem__(self, key, value):
        if key in self.dict:
            self.dict[key] = value    
            return

        if len(self.dict) >= self.max_size:
      ...

Quelques remarques à ce sujet

  • Il serait tentant pour certains de sous-classer dict ici. Vous pouvez techniquement le faire, mais c'est sujet à des bogues car les méthodes ne dépendent pas les unes des autres. Vous pouvez utiliser UserDict.DictMixin pour éviter de devoir définir toutes les méthodes. Il y a peu de méthodes que vous pourrez réutiliser si vous sous-classez dict .
  • Un dict ne sait pas quelle est la clé la moins récemment ajoutée, puisque les dicts ne sont pas ordonnés.
    • La version 2.7 introduira collections.OrderedDict mais pour l'instant, garder les clés dans l'ordre séparément devrait fonctionner correctement (utilisez un fichier collections.deque comme une file d'attente).
    • Si l'obtention de la plus ancienne n'est pas si importante, vous pouvez simplement utiliser la fonction popitem pour supprimer un élément arbitraire.
  • J'ai interprété le terme "plus ancien" comme signifiant "première insertion", approximativement. Il faudrait faire quelque chose d'un peu différent pour éliminer les éléments LRU. La stratégie efficace la plus évidente consisterait à conserver une liste de clés doublement liées avec des références aux noeuds eux-mêmes stockés comme valeurs dict (avec les valeurs réelles). Cela devient plus compliqué et son implémentation en Python pur entraîne beaucoup de surcharge.

0 votes

Vous avez en fait besoin de quelque chose de beaucoup plus complexe qu'un dict+deque si vous voulez O(1) get-/set-/delitem, bien que bien sûr cela ne compte vraiment que pour les grandes tailles.

0 votes

Vous pouvez réutiliser directement environ la moitié des méthodes de dict, OrderedDict, ou autre base même sans utiliser DictMixin. Il n'y a pas vraiment de problème à écrire des méthodes de transfert pour les autres, certainement pas plus que de devoir les écrire toutes soi-même, comme vous l'avez fait ici.

0 votes

J'essaie de trouver ce qui pourrait résoudre le problème en question. dict+deque vous donne O(1) get, set et delitem si vous vous souciez un peu de l'ordre des clés et que vous ne tombez pas dans le cas pathologique où il y a beaucoup de suppressions et de remises en place des mêmes clés, conduisant à ce que le deque soit pollué par des clés qui ne sont pas dans le dict.

2voto

c089 Points 1752

Vous pouvez créer une classe de dictionnaire personnalisée en sous-classant dict. Dans votre cas, vous devrez surcharger la classe __setitem__ de vérifier votre propre longueur et de supprimer quelque chose si la limite est dépassée. L'exemple suivant affiche la longueur actuelle après chaque insertion :

class mydict(dict):
    def __setitem__(self, k, v):
        dict.__setitem__(self, k, v)
        print len(self)

d = mydict()
d['foo'] = 'bar'
d['bar'] = 'baz'

2 votes

Le sous-classement de modules intégrés comme dict est souvent infructueuse. Dans des circonstances normales avec le sous-classement, les méthodes telles que update y setdefault s'appuierait sur la prépondérance __getitem__ mais ce n'est pas ainsi que cela fonctionne ici. La sous-classification des buildins permet d'introduire facilement des bogues difficiles à voir. Lorsque vous éliminez tous ces bogues, vous n'avez vraiment pas économisé de travail en sous-classant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X