【问题标题】:How to limit the size of a dictionary?如何限制字典的大小?
【发布时间】:2011-01-27 02:44:15
【问题描述】:

我想在 python 中使用 dict,但将键/值对的数量限制为 X。换句话说,如果 dict 当前存储 X 个键/值对并且我执行插入,我会就像要删除的现有对之一。如果它是最近最少插入/访问的密钥,那就太好了,但这不是完全必要的。

如果标准库中存在这个,请节省我一些时间并指出来!

【问题讨论】:

  • 很好的发现。不过我想保留这个,因为我并不特别需要 lru。
  • @Nick:限制大小似乎足以成为一个不同的问题,但是是的,这个问题是这个问题的一半。

标签: python caching dictionary lru


【解决方案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)

您还必须重写可以插入项目的其他方法,例如updateOrderedDict 的主要用途是让您可以轻松控制弹出的内容,否则正常的 dict 将起作用。

【讨论】:

  • @Mike: (self, size_limit=None, *args, **kwds) 是错误的,仅关键字参数 ((self, *args, size_limit=None, **kwds)) 不在当前 2.x 中。 (它们是否被考虑用于 2.7?不管它们不是 2.6。)此代码几乎适用于 OP 可能使用的任何版本,使 size_limit 有效地成为仅限关键字的参数,并维护与dicts相同的界面。
  • 你在 Python 2.7 上测试过你的代码吗? dict.pop 至少需要 1 个参数。 dict.popitem() 有效,但它删除了最近的项目。
  • 另外,setitem 应该在 实际设置项目之前检查大小限制,以免最终丢失正在设置的项目!跨度>
  • 准确地说,这并不是真正的 LRU 实现。这是由于字典大小限制而删除的 FIFO。为了拥有完整的 LRU 实现,需要重写 __contains__ 方法以将“使用”或查询的最后一项移动到 dict 链表的顶部。 [虽然我明白,这不是问题的主要目标]
  • 我认为这个答案的评价太高了,因为它没有显示如何实现可能还需要的任何其他方法——这不仅需要包括那些添加/插入元素的方法正如作者所提到的,还有任何删除/删除它们的。
【解决方案2】:

cachetools 将为您提供很好的 Mapping Hashes 实现(它适用于 python 2 和 3)。

文档摘录:

就本模块而言,缓存是固定数据的可变映射 最大尺寸。当缓存已满时,即通过添加另一个项目 缓存将超过其最大大小,缓存必须选择哪些项目 根据合适的缓存算法丢弃。

【讨论】:

    【解决方案3】:

    这是一个简单的无 LRU Python 2.6+ 解决方案(在较旧的 Python 中,您可以使用 UserDict.DictMixin 执行类似的操作,但在 2.6 及更高版本中不建议这样做,并且无论如何,collections 的 ABC 更可取... ):

    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 的显式委托是样板文件,但它确实保证所有其他方法都由@987654325 正确提供@。

    【讨论】:

      【解决方案4】:

      这是一个简单而高效的 LRU 缓存,使用简单的 Python 代码编写,可在任何 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))
      

      【讨论】:

      • 哇,你在这么短的时间内为各种用户案例编写了很多 LRU,从 python 食谱(activestate)、python 标准库、博客、推特中阅读你的 Python 代码总是很高兴,或 pycon 讲座,现在也在 stackoverflow 上。
      • 这个实现看起来不是pythonic。为什么不使用OrderedDictMutableMapping 来执行此操作...
      • 它是完美的 Pythonic —— 一个简单的类,直接使用字典、列表、基本分配和解包。逻辑是自包含的,没有外部依赖。这段代码也非常快,并且很容易被 PyPy 进一步优化。 OrderedDict 增加了空间开销(它在内部使用了两个字典,而这只使用了一个),并且它执行了本次使用不需要的不必要的工作。在这种情况下,MutableMapping 不提供任何有用的功能。
      • PREVNEXTKEYVALUE 的类级别定义有什么意义,因为它们没有在任何方法中使用?
      • @martineau 它只是为了显示链接的结构。
      【解决方案5】:

      您可以通过子类化 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'
      

      【讨论】:

      • dict 这样子类化内置函数通常是徒劳的。在具有子类化的正常情况下,updatesetdefault 之类的方法将依赖于覆盖的 __getitem__,但这不是这里的工作方式。子类化内置函数可以很容易地引入难以看到的错误。当您消除所有这些错误时,您实际上并没有通过子类化节省任何工作。
      【解决方案6】:

      字典没有这种行为。您可以创建自己的类来执行此操作,例如

      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 子类化,那么您可以重复使用的方法很少。
      • dict 不知道最近最少添加的键是什么,因为 dict 是无序的。
        • 2.7 将引入collections.OrderedDict,但现在分别保持键的顺序应该可以正常工作(使用collections.deque 作为队列)。
        • 如果获取最旧的不是那么重要,您可以使用popitem 方法删除任意一项。
      • 我将最早的解释为大约第一次插入。您必须做一些不同的事情来消除 LRU 项目。最明显的有效策略是保留一个双向链接的键列表,其中包含对节点本身的引用,这些节点本身存储为 dict 值(以及实际值)。这变得更加复杂,并且在纯 Python 中实现它会带来很多开销。

      【讨论】:

      • 如果你想要 O(1) get-/set-/delitem,你实际上需要比 dict+deque 复杂得多的东西,当然这只对更大的尺寸很重要。
      • 即使不使用 DictMixin,您也可以直接重用 dict、OrderedDict 或其他基础中大约一半的方法。为其他人编写转发方法似乎并不容易出错,当然也不会比你自己编写它们更容易出错,就像你在这里一样。
      • 我正在努力寻找有希望解决手头问题的方法。 dict+deque 如果您只关心键的有序性并且不会陷入对相同键进行多次删除和重新设置的病态情况,那么 dict+deque 会为您提供 O(1) 获取、设置和删除,导致双端队列被不在字典中的键污染。
      • 不,对于 delitem,您必须在双端队列中搜索已删除的项目,这是 O(n)。查看其中一种纯 Python OrderedDict 实现。
      • 大多数时候我看到dict 在野外被子类化,编码器忘记覆盖所有适当的方法。这意味着由此产生的错误有机会默默地通过,这真的很糟糕。在这种情况下,我真的看不到从子类化中获得什么。
      【解决方案7】:

      已经有很多很好的答案,但我想指出一个简单的、pythonic 的 LRU 缓存实现。这类似于 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)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-25
        • 1970-01-01
        • 2011-01-19
        • 2019-11-13
        • 2019-02-27
        相关资源
        最近更新 更多