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 l'une des les paires existantes à supprimer. Ce serait bien s'il s'agissait de la clé insérée/d'accès la moins récente, mais ce n'est pas complètement nécessaire.
Si cela existe dans la bibliothèque standard, veuillez me faire gagner du temps et le signaler!
Python 2.7 et 3.1 ont OrderedDict et il existe des implémentations en Python pur pour les Pythons antérieurs.
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'usage principal de OrderedDict
est que vous pouvez contrôler ce qui est facilement sauté, sinon un dict
normal fonctionnerait.
cachetools vous fournira une belle implémentation de Mapping Hashes qui fait 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'une taille maximale fixe. Lorsque le cache est plein, c'est-à-dire qu'en ajoutant un autre élément, le cache dépasserait sa taille maximale, le cache doit choisir le ou les éléments à rejeter en fonction d'un algorithme de cache approprié.
Voici une solution simple, sans LRU Python 2.6+) (dans les anciens Pythons, vous pourriez faire quelque chose de similaire avec UserDict.DictMixin
, mais en 2.6 et mieux ce n'est pas recommandé, et les ABC de collections
sont quand même 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 ne voudrez probablement pas sous-classer dict - la délégation explicite à self.d
est malheureusement passe-partout, mais garantie que toutes les autres méthodes sont correctement fournies par collections.MutableMapping
.
Voici un cache LRU simple et efficace écrit avec de la saleté simple Python code qui s'exécute sur n'importe quel python version 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 __== '__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))
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
dict
ici. Vous pouvez techniquement le faire, mais cela est sujet aux bogues car les méthodes ne dépendent pas les unes des autres. Vous pouvez utiliser UserDict.DictMixin
pour éviter d'avoir à définir toutes les méthodes. Il y a peu de méthodes que vous pourriez réutiliser si vous sous-classe dict
.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).popitem
pour supprimer un élément arbitraire.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 recalculé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'
Il y a eu beaucoup de bonnes réponses, mais je veux souligner une implémentation Pythonic 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)