使用pickle将巨大的二元字典保存到文件

Posted

技术标签:

【中文标题】使用pickle将巨大的二元字典保存到文件【英文标题】:Saving huge bigram dictionary to file using pickle 【发布时间】:2011-01-07 16:11:42 【问题描述】:

我的一个朋友写了这个小程序。 textFile 大小为 1.2GB(价值 7 年的报纸)。 他成功地创建了字典,但无法使用 pickle 将其写入文件(程序挂起)。

import sys
import string
import cPickle as pickle

biGramDict = 

textFile = open(str(sys.argv[1]), 'r')
biGramDictFile = open(str(sys.argv[2]), 'w')


for line in textFile:
   if (line.find('<s>')!=-1):
      old = None
      for line2 in textFile:
         if (line2.find('</s>')!=-1):
            break
         else:
            line2=line2.strip()
            if line2 not in string.punctuation:
               if old != None:
                  if old not in biGramDict:
                     biGramDict[old] = 
                  if line2 not in biGramDict[old]:
                     biGramDict[old][line2] = 0
                  biGramDict[old][line2]+=1
               old=line2

textFile.close()

print "going to pickle..."    
pickle.dump(biGramDict, biGramDictFile,2)

print "pickle done. now load it..."

biGramDictFile.close()
biGramDictFile = open(str(sys.argv[2]), 'r')

newBiGramDict = pickle.load(biGramDictFile)

提前致谢。

编辑 对于任何有兴趣的人,我将简要解释这个程序的作用。 假设您的文件格式大致如下:

<s>
Hello
,
World
!
</s>
<s>
Hello
,
munde
!
</s>
<s>
World
domination
.
</s>
<s>
Total
World
domination
!
</s>
&lt;s&gt;句子分隔符。 每行一个字。

生成一个 biGramDictionary 供以后使用。 像这样:


 "Hello": "World": 1, "munde": 1, 
 "World": "domination": 2,
 "Total": "World": 1,

希望这会有所帮助。现在策略改为使用 mysql,因为 sqlite 不起作用(可能是因为大小)

【问题讨论】:

如果您要处理大文件,为什么不使用数据库呢?另外,我看到你在同一个文件上循环了 2 次,这可能是多余的并增加了处理成本。为什么不描述您对示例输入文件所做的事情? ghostdog74,你看到 2 个 for 语句,但文件上只有一个循环 :) 迭代文件只是读取行(从实际位置),它不会寻找到开头文件。 只需尝试sqlitedict(您的 Python 字典由磁盘上的 DB 支持,而不是 RAM)。 【参考方案1】:

Pickle 仅用于编写完整的(小)对象。您的字典有点大,甚至无法存储在内存中,您最好使用数据库代替,这样您就可以一个一个地存储和检索条目,而不是一次全部存储和检索。

可以在 Python 中使用的一些良好且易于集成的单一文件数据库格式是 SQLite 或 DBM variants 之一。最后一个就像字典一样(即您可以读取和写入键/值对),但使用磁盘作为存储而不是 1.2 GB 的内存。

【讨论】:

Sqlite 是一个完全关系型数据库,而 Berkeley DB 不是,只是键/值。如果只是存储,我认为 Berkeley 是一个更好的选择,而如果你想进行一些查询并以更有条理的方式存储信息,sqlite 更合适。 BerkeleyDB 变化无常且难以管理,尤其是在处理大量数据的情况下。即使对于单个字符串->字符串存储(这就是 BerkeleyDB 的用途),我也会使用 SQLite,它会负责所有 BerkeleyDB 管理。 SQLite 不像字典。 bsddb 模块 (python.org/doc/2.6/library/bsddb.html) 的 Python 页面说它已被弃用。 BSD DB 是否还有另一个不推荐使用的 Python 选项? python.org/doc/2.6/library/persistence.html 列出了许多数据持久性模块。 gdbm 模块看起来非常相似并且仍然受支持,我会选择那个。【参考方案2】:

您真的需要内存中的全部数据吗?如果您想要字典/pickle 方法,您可以以简单的方式拆分它,例如每年或每月一个文件。

另外,请记住字典没有排序,您可能会遇到必须对大量数据进行排序的问题。如果您想搜索或排序数据,当然...

无论如何,我认为之前评论的数据库方法是最灵活的一种,特别是从长远来看...

【讨论】:

【参考方案3】:

如果您的真的,真的想要使用类似语义的字典,请尝试 SQLAlchemy 的 associationproxy。以下(相当长的)代码将您的字典翻译成entries-Table 中的Key,Value-Pairs。我不知道 SQLAlchemy 是如何处理你的大字典的,但是 SQLite 应该能够很好地处理它。

from sqlalchemy import create_engine, MetaData
from sqlalchemy import Table, Column, Integer, ForeignKey, Unicode, UnicodeText
from sqlalchemy.orm import mapper, sessionmaker, scoped_session, Query, relation
from sqlalchemy.orm.collections import column_mapped_collection
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.schema import UniqueConstraint

engine = create_engine('sqlite:///newspapers.db')

metadata = MetaData()
metadata.bind = engine

Session = scoped_session(sessionmaker(engine))
session = Session()

newspapers = Table('newspapers', metadata,
    Column('newspaper_id', Integer, primary_key=True),
    Column('newspaper_name', Unicode(128)),
)

entries = Table('entries', metadata,
    Column('entry_id', Integer, primary_key=True),
    Column('newspaper_id', Integer, ForeignKey('newspapers.newspaper_id')),
    Column('entry_key', Unicode(255)),
    Column('entry_value', UnicodeText),
    UniqueConstraint('entry_key', 'entry_value', name="pair"),
)

class Base(object):

    def __init__(self, **kw):
        for key, value in kw.items():
            setattr(self, key, value)

    query = Session.query_property(Query)

def create_entry(key, value):
    return Entry(entry_key=key, entry_value=value)

class Newspaper(Base):

    entries = association_proxy('entry_dict', 'entry_value',
        creator=create_entry)

class Entry(Base):
    pass

mapper(Newspaper, newspapers, properties=
    'entry_dict': relation(Entry,
        collection_class=column_mapped_collection(entries.c.entry_key)),
)
mapper(Entry, entries)

metadata.create_all()

dictionary = 
    u'foo': u'bar',
    u'baz': u'quux'


roll = Newspaper(newspaper_name=u"The Toilet Roll")
session.add(roll)
session.flush()

roll.entries = dictionary
session.flush()

for entry in Entry.query.all():
    print entry.entry_key, entry.entry_value
session.commit()

session.expire_all()

print Newspaper.query.filter_by(newspaper_id=1).one().entries

给予

foo bar
baz quux
u'foo': u'bar', u'baz': u'quux'

【讨论】:

我正在考虑在 python 中使用 SQLite3。不过,我不确定您的回答中与sqlalchemy 的关系是什么。【参考方案4】:

一种解决方案是使用buzhug 代替pickle。这是一个纯 Python 解决方案,并保留了非常 Pythonic 的语法。我认为这是从搁置及其同类产品升级的下一步。它将处理您正在谈论的数据大小。它的大小限制为每个字段 2 GB(每个字段存储在单独的文件中)。

【讨论】:

【参考方案5】:

我从http://coverartarchive.org 捕获图像,虽然下载这么多图像很慢,但pickle 155 MB 没有问题:

$ ll
total 151756
-rw-rw-r--  1 rick rick 155208082 Oct 10 10:04 ipc.pickle

当我不再只为一张 CD 下载图像时,我会回来并用更大的泡菜限制更新这个答案。不幸的是,我还没有找到任何说明酸洗限制的地方......

【讨论】:

以上是关于使用pickle将巨大的二元字典保存到文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 pickle 保存字典?

保存数据到文件的模块(configparser,json,pickle,shelve,xml)_python

为啥 pickle.dump(obj) 与 sys.getsizeof(obj) 的大小不同?如何将变量保存到文件文件?

『Pickle』数据结构持久化模块_常用方法记录

Python中Pickle模块的dump()方法和load()方法

pickle模块的基本使用