【问题标题】:How to store a big dictionary?如何存储一个大字典?
【发布时间】:2012-12-13 01:15:03
【问题描述】:

我有一个大字典 (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)]]}

【问题讨论】:

  • 您是否尝试过使用 JSON 将大字典存储在单独的文本文件中?
  • 也许数据库更适合存储这种大小的数据或使用文本格式
  • 你能从这个文件中摘录一两段吗?根据文件与有效 JSON 的接近程度,您可能能够打开文件进行读取并迭代解析它以将值写入另一个将来更易于访问的文件。
  • 好吧,搁置实际上一个数据库。是的,它是一个 BDB,而不是一个 sqlite DB,但对于 OP 的用例来说,这实际上可能更好。
  • @g.d.d.c 字典值包含列表列表

标签: python dictionary pickle


【解决方案1】:

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 等标准格式。 (当然,如果您需要与其他程序的互操作性,尤其是在其他语言中,则可以使用 JSON 之类的方法。)最好使用数据库;唯一的问题是,什么样的数据库。

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

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

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


来自 cmets,@ekta 想让我解释为什么存在对 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 调用。这可能对简单性/可读性和性能都更好。)

【讨论】:

  • 我认为问题不在于如何使用 cPickle(尽管有可能),而在于他在到达您的return 行之前遇到了内存错误。此外,您不需要上下文库来使用withshelve。他们自己提供__enter____exit__
  • @g.d.d.c: shelve 对象在 2.7 或 3.3 中都不提供 __exit__,因此您确实需要 contextlib。但是对于您的另一点,您是对的,直接制作架子可能比创建dict 然后shelve 它更有意义。我会编辑答案。
  • @ekta:至于性能,它实际上取决于您如何使用它们。当然,您可以比中等大小的字符串更快地散列中等大小的 int,但如果您有 very 个短字符串或巨大的 int,那就不再适用了。此外,如果您传递相同的字符串对象(而不是恰好相等的独立字符串),哈希值将被缓存,使其比哈希中等大小的 int 更快。
  • @ekta:真的没有“一位浮点数”这样的东西。例如,1.1 不能完全由 float 表示;试试format(1.1, '.50') 看看你真的有什么。如果您想要 1 位十进制数字的精确值,请使用 decimal.Decimal(或第三方或自定义定点类型),而不是 float。 (更多信息请参见What Every Computer Scientist Should Know About Floating-Point。)
  • @ekta:同时,与其尝试为一对两个浮点数构建一个字符串表示,然后将其编码为 UTF-8,并通过解码和解析将其反转,不如找到一个更好的代表性。例如,struct.pack('>dd') 会将两个 float 值转换为 16 个字节,struct.unpack 会将它们取回。 (但同样,float 可能是一个错误。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-09-07
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 2019-10-04
相关资源
最近更新 更多