Ограничение размера словаря python

Я хотел бы работать с dict в python, но ограничить количество пар ключ/значение X. другими словами, если dict в настоящее время хранит пары ключ/значение X, и я выполняю вставку, я хотел бы, чтобы одна из существующих пар была удалена. Было бы неплохо, если бы это был наименее недавно вставленный/доступ к ключу, но это не совсем необходимо.

Если это существует в стандартной библиотеке, пожалуйста, сэкономьте мне время и укажите это!

7 ответов


Python 2.7 и 3.1 имеют OrderedDict и есть реализации pure-Python для более ранних Pythons.

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)

вам также придется переопределить другие методы, которые могут вставлять элементы, такие как update. Основное использование OrderedDict, так что вы можете контролировать то, что получает выскочил легко, в противном случае нормальный дикт будет работать.


cachetools предоставит вам хорошую реализацию хэшей отображения, которая делает это (и она работает на python 2 и 3).

выдержка из документации:

для этого модуля кэш является изменяемым отображением фиксированного максимальный размер. Когда кэш заполнен, т. е. путем добавления другого элемента, кэш превысит свой максимальный размер, кэш должен выбрать, какой элемент(ы) чтобы отказаться от зависимости от соответствующей кэша алгоритм.


вот простое, не - LRU Python 2.6 + решение (в старых Pythons вы можете сделать что-то подобное с UserDict.DictMixin, но в 2.6 и лучше это не рекомендуется, а Азбука от collections в любом случае предпочтительнее...):

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)

Как упоминалось в других ответах, вы, вероятно, не хотите подкласса dict -- явное делегирование себя.d, К сожалению, boilerplatey, но это делает гарантия что каждый другой метод правильно поставлен collections.MutableDict.


вот простой и эффективный кэш LRU, написанный с помощью простого кода Python dirt, который работает на любой версии python 1.5.2 или более поздней версии:

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))

dict не имеет такого поведения. Вы можете сделать свой собственный класс, который делает это, например, что-то вроде

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:
      ...

несколько заметок об этом

  • для некоторых было бы заманчиво подкласс dict здесь. Вы можете технически сделать это, но это подвержено ошибкам, потому что методы не зависят друг от друга. Вы можете использовать UserDict.DictMixin чтобы сохранить необходимость определения всех методов. Есть несколько методов, которые вы могли бы повторно использовать, если вы подкласс dict.
  • дикт не знает, что наименее недавно добавленный ключ, так как дикты неупорядочены.
    • 2.7 познакомим collections.OrderedDict, но пока держать ключи в порядке отдельно должно работать отлично (используйте collections.deque в очереди).
    • если старые еще не все, что важно, вы можете просто использовать popitem метод удаления одного произвольного элемента.
  • я interprettered старейших означать первой вставки, примерно. Вы пришлось бы сделать что-то немного другое, чтобы устранить элементы LRU. Наиболее очевидной эффективной стратегией было бы сохранение двусвязного списка ключей со ссылками на сами узлы, хранящиеся как значения dict (наряду с реальными значениями). Это становится более сложным, и реализация его в чистом Python несет много накладных расходов.

вы можете создать пользовательский класс словаря путем подкласса dict. В вашем случае вам придется переопределить __setitem__ чтобы проверить вашу собственную длину и удалить что-то если лимит recahed. Следующий пример будет печатать текущую длину после каждой вставки:

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

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

там было много хороших ответов, но я хочу отметить простой, обновления для реализации LRU-кэш. Это похоже на ответ Алекса Мартелли.

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)