Limiter la taille d'un dictionnaire python

Je voudrais 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 que l'une des paires existantes soit supprimée. Ce serait bien si c'était la clé/accès la moins récemment insérée mais ce n'est pas complètement nécessaire.

Si cela existe dans la bibliothèque standard, économisez-moi du temps et signalez-le!

47
demandé sur anthony 2010-03-13 10:19:49

7 réponses

Python 2.7 et 3.1 ont OrderedDict et il existe des implémentations Python Python pour les Pythons précédents.

from collections import OrderedDict

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)

Vous devrez également remplacer d'autres méthodes pouvant insérer des éléments, telles que update. L'utilisation principale de OrderedDict est de sorte que vous pouvez contrôler ce qui est sauté facilement, sinon un dict normal fonctionnerait.

37
répondu Charles 2016-06-02 01:24:21

Cachetools vous fournira une belle implémentation des hachages de mappage qui font cela (et cela fonctionne sur python 2 et 3).

Extrait de la documentation:

Aux fins de ce module, un cache est un mappage mutable d'un taille maximale. Lorsque le cache est plein, c'est à dire en ajoutant un autre élément de la le cache dépasser sa taille maximale, le cache doit choisir l'élément(s) à ignorer en fonction d'un algorithme de cache approprié.

15
répondu vaab 2015-02-02 03:15:36

Voici une solution simple et sans LRU python 2.6+ (dans les anciens Pythons, vous pouvez faire quelque chose de similaire avec UserDict.DictMixin, mais dans 2.6 et mieux ce n'est pas recommandé, et les ABC de collections sont préférables de toute façon...):

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 ne voulez probablement pas sous-classer dict - la délégation explicite à self.d est malheureusement boilerplatey mais il garantit que toutes les autres méthodes sont correctement fournies par collections.MutableDict.

8
répondu Alex Martelli 2010-03-13 15:45:44

Voici un cache LRU simple et efficace écrit avec du code Python simple dirt qui s'exécute sur n'importe quelle version python 1.5.2 ou ultérieure:

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))
7
répondu Raymond Hettinger 2011-11-30 23:49:34

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 notes à ce sujet

  • Il serait tentant pour certains de sous-classer dict ici. Vous pouvez techniquement le faire, mais il est sujet aux bugs parce que les méthodes ne dépendent pas les unes des autres. Vous pouvez utiliser UserDict.DictMixin pour enregistrer la nécessité de définir toutes les méthodes. Il y a peu de méthodes que vous pourriez réutiliser si vous sous-classez dict.
  • un dict ne sait pas ce que le la clé la moins récemment ajoutée est, puisque les dicts ne sont pas ordonnés.
    • 2.7 introduira collections.OrderedDict, mais pour l'instant garder les clés dans l'ordre séparément devrait fonctionner correctement (utilisez un collections.deque comme file d'attente).
    • Si obtenir le plus ancien n'est pas si important, vous pouvez simplement utiliser la méthode popitem pour supprimer un élément arbitraire.
  • j'ai interprété le plus ancien comme signifiant la première insertion, approximativement. Vous devriez faire quelque chose d'un peu différent pour éliminer les éléments LRU. Le plus évident une stratégie efficace impliquerait de conserver une liste de clés doublement liée avec des références aux nœuds eux-mêmes stockées sous forme de valeurs dict (avec les valeurs réelles). Cela devient plus compliqué et l'implémenter en Python pur entraîne beaucoup de frais généraux.
2
répondu Mike Graham 2010-03-13 08:18:18

Vous pouvez créer une classe de dictionnaire personnalisée en sous-classant dict. Dans votre cas, vous devrez remplacer __setitem__ pour vérifier votre propre longueur et supprimer quelque chose si la limite est recahée. L'exemple suivant imprimerait 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'
1
répondu c089 2010-03-13 07:28:45

Il y a eu beaucoup de bonnes réponses, mais je veux souligner une implémentation pythonique simple pour le cache LRU. C'est similaire à la réponse D'Alex Martelli.

from collections import OrderedDict, MutableMapping

class Cache(MutableMapping):
    def __init__(self, maxlen, items=None):
        self._maxlen = maxlen
        self.d = OrderedDict()
        if items:
            for k, v in items:
                self[k] = v

    @property
    def maxlen(self):
        return self._maxlen

    def __getitem__(self, key):
        self.d.move_to_end(key)
        return self.d[key]

    def __setitem__(self, key, value):
        if key in self.d:
            self.d.move_to_end(key)
        elif len(self.d) == self.maxlen:
            self.d.popitem(last=False)
        self.d[key] = value

    def __delitem__(self, key):
        del self.d[key]

    def __iter__(self):
        return self.d.__iter__()

    def __len__(self):
        return len(self.d)
1
répondu Patrick Chen 2017-06-14 15:35:31