Как ограничить размер словаря?

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

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

Ответ 1

Python 2.7 и 3.1 имеют OrderedDict, и для более ранних Python существуют реализации с чистым Python.

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)

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

Ответ 2

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

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

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

Ответ 3

Вот простое, не-LRU Python 2. 6+ решение (в более старых Pythons вы могли сделать что-то похожее с UserDict.DictMixin, но в UserDict.DictMixin 2.6 и лучше, что не рекомендуется, и ABC из 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 - явное делегирование self.d, к сожалению, является шаблонным, но оно гарантирует, что любой другой метод должным образом предоставляется collections.MutableMapping self.d.

Ответ 4

Вот простой и эффективный кеш LRU, написанный с простым кодом 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))

Ответ 5

У 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.
  • Диктат не знает, что последний добавленный ключ, так как dicts неупорядочены.
    • 2.7 представит collections.OrderedDict, но в настоящее время хранение ключей в порядке по отдельности должно работать нормально (используйте collections.deque в качестве очереди).
    • Если получить старое, это не все, что важно, вы можете просто использовать метод popitem для удаления одного произвольного элемента.
  • Я интерпретировал старейшее значение для первой вставки приблизительно. Вы должны сделать что-то немного другое, чтобы устранить элементы LRU. Наиболее очевидной эффективной стратегией будет сохранение двусвязного списка ключей со ссылками на сами узлы, хранящиеся в виде значений dict (вместе с реальными значениями). Это усложняется, и реализация его в чистом Python несет много накладных расходов.

Ответ 6

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

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

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

Ответ 7

Было много хороших ответов, но я хочу указать простую, питоновую реализацию для кеша 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)