如何为SAX解析器构建正确的生成器函数
我有一个35.5Mb的.XLSM文件。当实际可用的内容变多时,DOM解析器像element tree这样的工具会因为内存耗尽而运行很久,最后崩溃。
不过,当我使用SAX解析器时,ContentHandler
似乎只能把行数据存到一个临时文件里。这让我有点烦,因为解析器和主应用程序本来可以有一个简单的协作关系,每解析一行就可以把它交给应用程序。
看起来下面的做法是行不通的。
def gen_rows_from_xlsx( someFile ):
myHandler= HandlerForXLSX()
p= xml.sax.makeParser()
p.setContentHandler( myHandler, some_kind_of_buffer )
for row in some_kind_of_buffer.rows():
p.parse() # Just enough to get to the ContentHandler's "buffer.put()"
yield row
定期,HandlerForXLSX
会调用some_kind_of_buffer.put( row )
把一行数据放进缓冲区。这一行数据应该通过some_kind_of_buffer.rows()
来获取。
如果SAX解析器和gen_rows_from_xslx()
之间能有一个简单的协作关系,那就太好了。
我是不是忽略了什么生成器函数的技巧,可以让我把SAX当作某种协作程序来用?
难道唯一的办法就是创建一个SAX解析线程,然后用Queue
来获取解析器生成的行数据吗?
还是说更简单的办法是直接在SAX解析器里创建一个临时文件,然后通过生成器来输出这些对象?
相关链接: 懒惰的SAX XML解析器,支持暂停/恢复。
2 个回答
看起来你可以用 IncrementalParser
接口来实现这个功能?大概可以这样做:
def gen_rows_from_xlsx(someFile):
buf = collections.deque()
myHandler = HandlerForXLSX(buf)
p = xml.sax.make_parser()
p.setContentHandler(myHandler)
with open(someFile) as f:
while True:
d = f.read(BLOCKSIZE)
if not d: break
p.feed(d)
while buf: yield buf.popleft()
p.close()
如果你想用 parse
来实现这个,你就得在多个堆栈帧之间进行 yield
,但这在 Python 中是根本不支持的。
我有一个35.5Mb的.XLSM文件。当里面实际可用的内容展开后,DOM解析器像element tree一样会消耗大量内存,导致运行时间变得非常长。
我不太明白这个意思。你应该使用的东西有:
import xml.etree.cElementTree as ET
ET.iterparse(sourcefile) # sourcefile being a cStringIO.StringIO instance holding your worksheet XML document
element.clear() # leave only scorched earth behind you
这篇文章展示了如何使用iterparse
和clear
。
举个例子:把一个100Mb的XLSX文件(里面有两个工作表,每个大约有16000行和200列)加载到xlrd对象模型中:
耗时大约4分钟(在一台老旧的笔记本电脑上运行,配置是2 GHz单核,操作系统是Windows XP,使用的是Python 2.7)。内存使用量最高大约是300Mb,大部分是输出数据,而不是元素树。