Cómo limitar el tamaño de un diccionario?
-
19-09-2019 - |
Pregunta
Me gustaría trabajar con un diccionario en Python, pero limitar el número de pares clave / valor de X. En otras palabras, si el dict está almacenando pares clave X / valor y realizar una inserción, lo haría como uno de los pares existentes que se retiren. Sería bueno si era la menos recientemente insertado / accede clave, pero eso no es completamente necesario.
Si esto existe en la biblioteca estándar por favor me ahorrar algo de tiempo y señalarlo!
Solución
Python 2.7 y 3.1 tienen OrderedDict y hay implementaciones puras-Python para pitones anteriores.
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)
Se podría también tener que reemplazar otros métodos que se pueden insertar elementos, tales como update
. El uso principal de OrderedDict
es para que pueda controlar lo que se metió con facilidad, por lo demás un dict
normal sería trabajar.
Otros consejos
cachetools le proporcionará buena aplicación de la cartografía hashes que hace esto (y funciona en python 2 y 3).
Extracto de la documentación:
A los efectos de este módulo, una memoria caché es un mapeo de un mutable fijo talla máxima. Cuando la caché está llena, es decir, añadiendo otro elemento de la caché excedería su tamaño máximo, el caché debe elegir qué elemento (s) para descartar basado en un algoritmo de caché adecuado.
Esto es un simple, no-LRU solución Python 2.6+ (en pitones de edad avanzada se podría hacer algo similar con UserDict.DictMixin
, pero en 2.6 y mejor que no es recomendable, y el ABC de collections
son preferibles todos modos ...):
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))
Como otras respuestas mencionadas, es probable que no quiere subclase dict - la delegación explícita a self.d
Sentimos boilerplatey pero lo hace Garantía que todos los demás métodos se suministra adecuadamente por collections.MutableMapping
Este es un simple y eficiente caché LRU escrita con un simple código Python suciedad que se ejecuta en cualquier versión de Python 1.5.2 o posterior:
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))
A dict no tiene este comportamiento. Se podría hacer su propia clase que hace esto, por ejemplo, algo así como
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:
...
Algunas notas acerca de este
- Sería tentador para algunos subclase
dict
aquí. Se puede hacer técnicamente esto, pero es propenso a error debido a que los métodos no dependen unos de otros. Se puede utilizar para guardarUserDict.DictMixin
tener que definir todos los métodos. Hay algunos métodos que sería capaz de reutilización sidict
subclase. - Una dict no sabe cuál es la clave menos recientemente añadido es, ya que predice no están ordenados.
- 2.7 introducirá
collections.OrderedDict
, pero por ahora mantener las llaves con el fin separado debería funcionar bien (usar uncollections.deque
como una cola). - Si conseguir el más antiguo no es tan imporante, sólo se puede utilizar el método
popitem
eliminar un elemento arbitrario.
- 2.7 introducirá
- I interprettered más antigua en el sentido de la primera inserción, aproximadamente. Usted tendría que hacer algo un poco diferente para eliminar los elementos LRU. La estrategia eficiente más obvia sería implicar el mantenimiento de una lista doblemente enlazada de llaves con referencias a los nodos mismos almacenan como valores dict (junto con los valores reales). Esto se vuelve más complicado y su implementación en Python puro lleva un montón de gastos.
Puede crear una clase de diccionario personalizado subclasificando dict. En su caso, se tendría que anular __setitem__
tener comprobar su propia longitud y borrar algo si se recahed el límite. El siguiente ejemplo imprimiría la longitud actual después de cada inserción:
class mydict(dict):
def __setitem__(self, k, v):
dict.__setitem__(self, k, v)
print len(self)
d = mydict()
d['foo'] = 'bar'
d['bar'] = 'baz'
Ha habido muchas buenas respuestas, pero quiero señalar una implementación sencilla, Pythonic de caché LRU. Es similar a la respuesta de 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)