4

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

如果我执行语句:

from MyDict import MyDict

MemoryError抛出异常。

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

如何将此MyDict.py文件写入cPickleshelve不访问 MyDict。

这个 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)]]}
4

1 回答 1

12

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

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)

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

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

import cPickle

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

pickle对于必须保存和加载的任何其他持久性格式(无论是您自己编写的自定义内容,还是 JSON 或 YAML 之类的标准内容),go 都会遇到相同的基本问题。(当然,如果您需要与其他程序的互操作性,尤其是在其他语言中,则可以使用 JSON 之类的方法。)最好使用数据库;唯一的问题是,什么样的数据库。

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

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

其他选项——<code>sqlite3、各种现代 nosql 数据库等——要求您更改访问数据的方式,甚至是组织数据的方式。(“列表列表”并不是一个明确的 ER 模型。)当然,从长远来看,这可能会带来更好的设计,所以如果您认为您真的应该使用关系模型,请考虑这个想法。


dbm从评论中,@ekta 希望我解释为什么存在一些限制shelve

首先,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,反斜杠转义,并用换行符连接它们。等等。对于许多特定领域,有一个简单的答案;没有适用于大多数领域的通用答案。

因此,如果您想使用 adbm来使用三个 UTF-8 字符串的元组作为键,您可以编写自己的包装器dbm(or shelve)。与 stdlib 中的许多模块一样,shelve它旨在提供有用的示例代码以及可用的功能,这就是文档具有指向源代码的链接的原因。这很简单,新手应该能够弄清楚如何对其进行分叉、子类化或包装以进行自己的自定义密钥编码。(请注意,如果您 wrap ,shelve您将不得不将您的自定义值编码为,str以便它可以将其编码为strbytesbytesstruct.pack调用上面。这对于简单性/可读性和性能来说可能会更好。)

于 2012-12-13T01:22:00.567 回答