【问题标题】:How to use large dicts in Python which not fit in memory?如何在 Python 中使用不适合内存的大字典?
【发布时间】:2013-12-19 15:45:57
【问题描述】:

我们使用dict,其中包含大约 4GB 的数据用于数据处理。方便快捷。

我们遇到的问题是这个dict 可能会增长超过 32GB。

我正在寻找一种使用dict 的方法(就像带有get() 方法等的变量),它可以大于可用内存。如果dict 以某种方式将数据存储在磁盘上并在调用get(key) 并且key 的值不在内存中时从磁盘检索数据,那就太好了。

我最好不要使用外部服务,比如 SQL 数据库。

我确实找到了Shelve,但它似乎也需要内存。

关于如何解决这个问题的任何想法?

【问题讨论】:

  • SQL 数据库不必是“外部服务”——您可以使用 SQLite。
  • 你说你找到了搁置,但你试过了吗?我很确定它可以满足您的要求。
  • 看来shelve 需要内存来存储您放入其中的所有内容。它并不是只将部分数据存储在内存中,而将其余数据存储在磁盘中。

标签: python memory data-structures dictionary


【解决方案1】:

听起来您可以使用目前在 No-SQL 的流行语下大肆宣传的键值存储。可以找到关于它的很好的介绍,例如在

http://ayende.com/blog/4449/that-no-sql-thing-key-value-stores.

它只是一个带有您描述的 API 的数据库。

【讨论】:

    【解决方案2】:

    我找不到任何(快速)模块来执行此操作,并决定创建自己的(我的第一个 python 项目 - 感谢@blubber 提供一些想法:P)。你可以在 GitHub 上找到它:https://github.com/ddofborg/diskdict 欢迎评论!

    【讨论】:

    • 请记住,当您使用 dict 时,其值 (list/dict/object) 将作为参考保存。当您腌制字典时,它的所有值也会被腌制,但不会作为参考。这意味着当您更新值时,它不会被重新腌制并变得过时。
    【解决方案3】:

    如果您不想使用 SQL 数据库(这是解决此类问题的合理解决方案),您必须找到一种方法来压缩您正在使用的数据或使用 a library like this one (或您自己的)自己进行映射到光盘。

    您还可以查看this question 了解更多策略。

    【讨论】:

      【解决方案4】:

      使用pickle 模块将字典序列化到磁盘。然后从字典的迭代器中获取连续的值并将它们最初放入缓存中。然后实现LRU等缓存方案;使用字典的 `popitem() 方法删除字典项,并在 LRU 的情况下添加先前访问的项。

      【讨论】:

        【解决方案5】:

        /EDIT:这现在是一个 Python 模块:fdict

        我遇到了类似的问题,但我正在处理嵌套字典。由于我的数据集非常嵌套,所有可用的解决方案都不适合我的用例:shelvechestshovesqlitezodb,以及任何其他 NoSQL 数据库主要在以下情况下工作你有一个级别,因为它们都腌制/JSON要序列化到数据库中的值,所以第一级之后的所有值都被序列化并且不可能对嵌套对象进行增量更新(你必须从加载整个分支一级节点,修改,然后放回去),如果嵌套对象本身很大,这是不可能的。

        这意味着当您有很多 1 级节点时,这些解决方案会有所帮助,但当您有少量 1 级节点和较深的层次结构时,则不会。

        为了解决这个问题,我在原生 Python dict 上创建了一个自定义 dict 类,以在内部以扁平形式表示嵌套的 dict:dict()['a']['b'] 将在内部表示为 dict()['a/b']

        然后在这个“内部扁平化”的字典之上,您现在可以使用任何对象到磁盘的解决方案,例如 shelve,这是我使用的,但您可以轻松地用其他东西替换。

        代码如下:

        import shelve
        
        class fdict(dict):
            '''Flattened nested dict, all items are settable and gettable through ['item1']['item2'] standard form or ['item1/item2'] internal form.
            This allows to replace the internal dict with any on-disk storage system like a shelve's shelf (great for huge nested dicts that cannot fit into memory).
            Main limitation: an entry can be both a singleton and a nested fdict, and there is no way to tell what is what, no error will be shown, the singleton will always be returned.
            '''
            def __init__(self, d=None, rootpath='', delimiter='/', *args):
                if d:
                    self.d = d
                else:
                    self.d = {}
                self.rootpath = rootpath
                self.delimiter = delimiter
        
            def _buildpath(self, key):
                return self.rootpath+self.delimiter+key if self.rootpath else key
        
            def __getitem__(self, key):
                # Node or leaf?
                if key in self.d: # Leaf: return the value
                    return self.d.__getitem__(key)
                else: # Node: return a new full fdict based on the old one but with a different rootpath to limit the results by default
                    return fdict(d=self.d, rootpath=self._buildpath(key))
        
            def __setitem__(self, key, value):
                self.d.__setitem__(self._buildpath(key), value)
        
            def keys(self):
                if not self.rootpath:
                    return self.d.keys()
                else:
                    pattern = self.rootpath+self.delimiter
                    lpattern = len(pattern)
                    return [k[lpattern:] for k in self.d.keys() if k.startswith(pattern)]
        
            def items(self):
                # Filter items to keep only the ones below the rootpath level
                if not self.rootpath:
                    return self.d.items()
                else:
                    pattern = self.rootpath+self.delimiter
                    lpattern = len(pattern)
                    return [(k[lpattern:], v) for k,v in self.d.items() if k.startswith(pattern)]
        
            def values(self):
                if not self.rootpath:
                    return self.d.values()
                else:
                    pattern = self.rootpath+self.delimiter
                    lpattern = len(pattern)
                    return [v for k,v in self.d.items() if k.startswith(pattern)]
        
            def update(self, d2):
                return self.d.update(d2.d)
        
            def __repr__(self):
                # Filter the items if there is a rootpath and return as a new fdict
                if self.rootpath:
                    return repr(fdict(d=dict(self.items())))
                else:
                    return self.d.__repr__()
        
            def __str__(self):
                if self.rootpath:
                    return str(fdict(d=dict(self.items())))
                else:
                    return self.d.__str__()
        
        class sfdict(fdict):
            '''A nested dict with flattened internal representation, combined with shelve to allow for efficient storage and memory allocation of huge nested dictionnaries.
            If you change leaf items (eg, list.append), do not forget to sync() to commit changes to disk and empty memory cache because else this class has no way to know if leaf items were changed!
            '''
            def __init__(self, *args, **kwargs):
                if not ('filename' in kwargs):
                    self.filename = None
                else:
                    self.filename = kwargs['filename']
                    del kwargs['filename']
                fdict.__init__(self, *args, **kwargs)
                self.d = shelve.open(filename=self.filename, flag='c', writeback=True)
        
            def __setitem__(self, key, value):
                fdict.__setitem__(self, key, value)
                self.sync()
        
            def get_filename(self):
                return self.filename
        
            def sync(self):
                self.d.sync()
        
            def close(self):
                self.d.close()
        

        fdictsfdict 都可以像标准的dict 一样使用(但我没有实现所有方法,呵呵)。

        完整代码在这里: https://gist.github.com/lrq3000/8ce9174c1c7a5ef546df1e1361417213

        这是在完整的 Python 模块中进一步开发的:fdict

        基准测试后,使用间接访问(即x['a']['b']['c'])时,这比字典慢大约 10 倍,而使用直接访问(即x['a/b/c'])时,速度大约一样快,尽管这里我不考虑shelve 保存到 anydbm 文件的开销,与 dict 相比,只是 fdict 数据结构的开销。

        【讨论】:

          猜你喜欢
          • 2012-03-03
          • 2011-01-13
          • 1970-01-01
          • 1970-01-01
          • 2017-04-08
          • 2014-03-30
          • 2023-03-05
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多