使用pickle将大型二元词典保存到文件中

8

我的一个朋友写了这个小程序。 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>
  • <s> 是句子分隔符。
  • 每行一个单词。

为之后使用生成了一个 biGramDictionary。
类似于这样:

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

希望这能有所帮助。现在的策略是使用 MySQL,因为 SQLite 无法正常工作(可能是由于文件大小问题)。


如果你要处理大文件,为什么不使用数据库呢?此外,我看到你在同一个文件上进行了两次for循环,这可能是多余的并增加了处理成本。为什么不使用示例输入文件来描述你正在做什么呢? - ghostdog74
1
ghostdog74,你看到了两个for语句,但实际上只有一个循环遍历文件 :) 遍历文件只是读取行(从实际位置),它不会寻找文件开头。 - Messa
仅需尝试[sqlitedict](https://pypi.python.org/pypi/sqlitedict)(使用磁盘上的数据库而不是RAM支持Python字典)。 - Radim
5个回答

11

Pickle只适用于写完整的(小型)对象。你的字典太大了,甚至无法在内存中容纳,最好使用数据库来存储和检索条目,而不是一次性全部操作。

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


Sqlite 是一个完全关系型数据库,而 Berkeley DB 不是,只有键/值。如果只是存储,我认为 Berkeley 是更好的选择,而如果你想进行一些查询并以更有组织的方式存储信息,sqlite 更合适。 - Khelben
1
BerkeleyDB相对而言比较棘手且难以管理,特别是在处理大量数据时。即使只是用来存储单个字符串->字符串存储(这正是BerkeleyDB所能做的),我也会使用SQLite,它将负责所有BerkeleyDB的管理工作。 - Thomas Wouters
SQLite不像字典一样工作。 - Thomas Wouters
1
Python的bsddb模块页面(http://www.python.org/doc/2.6/library/bsddb.html)显示该模块已被弃用。是否有其他非弃用的Python选项可用于BSD DB? - new name
http://www.python.org/doc/2.6/library/persistence.html列出了许多数据持久化模块。`gdbm`模块看起来非常相似且仍然得到支持,我会选择它。 - Wim
显示剩余3条评论

1

你真的需要将整个数据存储在内存中吗?如果你想使用字典/腌制方法,可以采用一些简单的方式进行拆分,比如每年或每月一个文件。

此外,请记住,字典是无序的,当你需要对大量数据进行排序时可能会遇到问题。当然,如果你想要搜索或排序数据,也可以考虑其他方法...

总之,我认为之前提到的数据库方法是最灵活的,特别是从长远来看...


1
一种解决方案是使用buzhug而不是pickle。这是一个纯Python解决方案,并保留非常Pythonic的语法。我认为它是从shelve及其类似物中迈出的下一步。它可以处理您所讨论的数据大小。其大小限制为每个字段2 GB(每个字段存储在单独的文件中)。

0
如果你真的非常想使用像语义字典这样的东西,可以尝试使用SQLAlchemy的associationproxy。下面这段(相当长的)代码将把你的字典转换成键值对存储在entries表中。我不知道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 有何关系。 - WinEunuuchs2Unix

0

我从 http://coverartarchive.org 捕获了图像,尽管下载这么多图像很慢,但是 pickle 处理 155 MB 没有问题:

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

当我不再仅仅为一个光盘下载图像时,我会回来更新这个答案并增加更大的pickle限制。不幸的是,我还没有找到任何地方说明了pickling的限制...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接