如何存储大字典?

时间:2012-12-13 01:15:04

标签: python dictionary pickle

我有一个大字典(28 MB)'MyDict'存储在MyDict.py文件中。

如果我执行声明:

from MyDict import MyDict

抛出MemoryError异常。

如何使用cPickleshelve模块访问此词典。

如何在不访问MyDict的情况下将此MyDict.py文件写入cPickleshelve

这个MyDict是通过写入文件生成的。 这是字典中的键值对:

{"""ABCD""" : [[(u'2011-03-21', 35.5, 37.5, 35.3, 35.85, 10434.0, 35.85), (u'2012-03-03', 86.0, 87.95, 85.55, 86.2, 30587.0, 86.2), (u'2011-03-23', 36.9, 36.9, 35.25, 36.1, 456.0, 36.1)],
    [(u'2011-03-18', 37.0, 38.0, 36.5, 36.5, 861.0, 36.5), (u'2012-03-03', 86.0, 87.95, 85.55, 86.2, 30587.0, 86.2), (u'2011-03-21', 35.5, 37.5, 35.3, 35.85, 10434.0, 35.85)],
    [(u'2011-03-16', 37.0, 37.9, 36.3, 36.7, 3876.0, 36.7), (u'2012-03-03', 86.0, 87.95, 85.55, 86.2, 30587.0, 86.2), (u'2011-03-21', 35.5, 37.5, 35.3, 35.85, 10434.0, 35.85)],
    [(u'2010-12-09', 40.5, 41.95, 36.3, 36.75, 42943.0, 36.75), (u'2011-10-26', 67.95, 71.9, 66.45, 70.35, 180812.0, 70.35), (u'2011-03-21', 35.5, 37.5, 35.3, 35.85, 10434.0, 35.85)],
    [(u'2009-01-16', 14.75, 15.0, 14.0, 14.15, 14999.0, 14.05), (u'2010-01-11', 50.0, 52.8, 49.0, 50.95, 174826.0, 50.95), (u'2009-01-27', 14.3, 15.0, 13.9, 14.15, 3862.0, 14.15)]]}

1 个答案:

答案 0 :(得分:9)

shelve实际上是一个不错的选择。它就像一个字典,但它由一个BDB(或类似的)键值数据库文件支持,Python将处理所有的缓存等,所以它不需要加载整个把事情当作记忆。

以下是如何创建搁置文件。请注意,架子键必须是字符串。另请注意,我是在原地创建货架,而不是先创建dict并搁置它。这样你就可以避免必须在内存dict中构建那个首先导致问题的巨型代价。

from contextlib import closing
import shelve

def makedict(shelf):
    # Put the real dict-generating code here, obviously
    for i in range(500000);
        shelf[str(i)] = i

with closing(shelve.open('mydict.shelf', 'c')) as shelf:
    makedict(shelf)

要使用它,实际上并没有阅读它;把它留在磁盘架上:

from contextlib import closing
import shelve

with closing(shelve.open('mydict.shelf')) as d:
    # Put all your actual work here.
    print len(d)

如果您的使用字典的代码不适合放入范围,请将with语句替换为普通open,并在您使用close时明确pickle。重做。

dict可能不是一个好主意,因为你仍然需要把整个事情都读到内存中。与导入定义巨型文字的模块相比,它可能会使用更少的瞬态内存,也可能使用磁盘空间,但仍然存在内存哈希表,这可能仍然存在问题。但你可以随时测试它,看看它的工作情况。

以下是如何创建pickle文件。请注意,您可以使用(几乎)任何您想要的键作为键,而不仅仅是字符串。但是,您必须先构建整个pickle,然后才能import cPickle def makedict(): # Put the real dict-generating code here, obviously return {i:i for i in range(500000)} with open('mydict.pickle', 'wb') as f: cPickle.dump(d, f, -1)

import cPickle

def loaddict():
    with open('mydict.pickle', 'rb') as f:
        return cPickle.load(f)

这会创建一个47MB的文件。

现在,要在您的主应用中使用它:

pickle

anydbm相同的基本问题是针对任何其他必须保存和加载的持久性格式 - 无论是您自己编写的自定义格式,还是JSON或YAML等标准格式。 (当然,如果你需要与其他程序的互操作性,特别是在其他语言中,可以采用类似JSON的方式。)你最好使用数据库;唯一的问题是,什么样的数据库。

dict类型数据库的优点是您可以像使用open一样使用它,而不必担心如何加载/保存/访问它({1}除外}和close行)。 anydbm的问题在于它只允许您将字符串映射到字符串。

shelve模块有效地包装anydbm,并对每个值进行酸洗。你的钥匙仍然必须是字符串,但你的价值几乎可以是任何东西。因此,只要您的密钥是字符串,并且您没有从值到外部对象的任何引用,它就是dict的非常透明的替代品。

其他选项 - sqlite3,各种现代nosql数据库等 - 要求您更改访问数据的方式,甚至是您组织数据的方式。 (A"列表清单"不清楚ER模型。)当然,从长远来看,这可能会带来更好的设计,所以如果你认为你真的应该使用关系模型,考虑一下这个想法。


根据评论,@ ikea希望我解释为什么存在对dbmshelve的某些限制。

首先,dbm可以追溯到70年代。一个可以简单有效地将8位字符串映射到字符串的数据库是一个非常大的交易。将所有类型的值存储为其字符串表示形式也是很常见的 - 或者,如果不是这样,那么只存储恰好代表当前机器上的值的字节。 (XML,JSON,甚至是字节顺序交换对于当时的机器来说可能过于昂贵,或者至少是当时的思考。)

扩展dbm以处理值的其他数据类型并不困难。它们永远不需要进行散列或比较,只需无损地存储和检索。由于pickle可以处理各种各样的类型,并且效率太低,并且附带Python,因此使用pickle是有意义的,因此shelve完全可以这一点。

但关键是一个不同的故事。您需要一种不仅无损可逆的编码,而且还要确保当且仅当它们实际上相等时,两个值才会编码为相等的字节。请记住,在Python中1 == True,但显然是pickle.dumps(1) != pickle.dumps(True)b'1' != b'True'等。

如果你只关心那种类型,有很多类型可以无损并且保持等式保存。例如,对于Unicode字符串,只需使用UTF-8。 (实际上,shelve会为您处理那个。)对于32位有符号整数,请使用struct.pack('>I')。对于三个字符串的元组,编码为UTF-8,反斜杠转义,并使用换行符连接它们。等等。对于许多特定领域,这是一个简单的答案;没有针对大多数域名的通用答案。

因此,如果您想使用dbm来使用三个UTF-8字符串的元组作为键,您可以围绕dbm(或shelve编写自己的包装器)。与stdlib中的许多模块一样,shelve旨在提供有用的示例代码以及可用的功能,这就是为the docs提供指向the source的链接的原因。它很简单,新手应该能够弄清楚如何分叉,子类化或包装它来做自己的自定义密钥编码。 (请注意,如果您打包shelve,则必须将自定义值编码为str,以便将str编码为bytes;如果您将其编码,或者将它子类化并覆盖相关方法,您可以直接编码到bytes - 例如,上面调用struct.pack。这对于简单性/可读性和性能可能更好。)

相关问题