文件对象的延迟读取以实现高效的批处理
lazyreader的Python项目详细描述
lazyreader是一个python模块,用于对文件对象进行延迟读取。
python标准库允许您一次读取一行文件,从而避免将整个文件加载到内存中。 例如:
withopen('large_file.txt')asf:forlineinf:print(line)
lazyreader允许您执行相同的操作,但是使用任意分隔符,并且对于任何呈现.read()方法的对象。 例如:
fromlazyreaderimportlazyreadwithopen('large_file.txt')asf:fordocinlazyread(f,delimiter=';'):print(doc)
这是我从Wellcome Digital Platform中派生出来的一段代码。 我们在s3中存储了大量的xml和json文件(有时是多个gbs),但每个文件实际上是一系列“文档”,由已知的分隔符分隔。 下载和解析整个文件的成本会高得吓人,但是lazyreader允许我们一次只在内存中保存一个文档。
安装
PYPI提供LazyReader:
$ pip install lazyreader
示例
如果我们有一个文件存储在本地,我们可以打开它并根据任何分隔符的选择进行拆分。 例如,如果我们有一个文本文件,其中的记录用逗号分隔:
withopen('lots_of_records.txt')asf:fordocinlazyread(f,delimiter=','):print(doc)
另一个例子:我们有一个文件存储在amazon s3中,我们希望逐行读取它。 boto3api为我们提供了一个从s3读取的文件对象:
importboto3client=boto3.client('s3')s3_object=client.get_object(Bucket='example-bucket',Key='words.txt')body=s3_object['Body']fordocinlazyread(body,delimiter=b'\n'):print(doc)
(这是最初编写此代码的用例。)
还有一个例子:我们正在获取一个html页面,并希望读取底层html中由<br>分隔的行。 喜欢这样:
importurllib.requestwithurllib.request.urlopen('https://example.org/')asf:fordocinlazyread(f,delimiter=b'<br>'):print(doc)
高级用法
lazyread()返回一个生成器,您可以包装它来构建一个生成器管道,这些生成器对数据进行处理。
第一个例子:我们有一个包含json对象列表的文件,每行一个。 (这是来自elasticdump的输出文件的格式。) 调用者真正需要的是python字典,而不是json字符串。 我们可以像这样包装lazyread():
importjsondeflazyjson(f,delimiter=b'\n'):fordocinlazyread(f,delimiter=delimiter):# Ignore empty lines, e.g. the last line in a fileifnotdoc.strip():continueyieldjson.loads(doc)
另一个例子:我们想解析一个大的XML文件,但不想一次将它全部加载到内存中。 我们可以编写以下包装:
fromlxmlimportetreedeflazyxmlstrings(f,opening_tag,closing_tag):fordocinlazyread(f,delimiter=closing_tag):ifopening_tagnotindoc:continue# We want complete XML blocks, so look for the opening tag and# just return its contentsblock=doc.split(opening_tag)[-1]yieldopening_tag+blockdeflazyxml(f,opening_tag,closing_tag):forxml_stringinlazyxmlstrings(f,opening_tag,closing_tag):yieldetree.fromstring(xml_string)
我们在wellcome使用这两个包装器来高效地处理保存在amazon s3中的大型文件。
作为一个模块,这不是有点简单吗?
也许吧。 堆栈溢出的方法与此非常相似,但我发现在独立模块中使用它很有用。
而且这并不完全是小事——至少对我来说不是。 我第一次写这篇文章时犯了两个错误:
我把最初运行的字符串硬编码为
running=b''
这只在文件对象返回bytestrings时有效。 如果它返回unicode字符串,那么当它第一次尝试从文件中读取时,会得到一个TypeError(无法将字节concat到str)。 字符串类型很重要!
从文件中再读取1024个字符后,我检查了分隔符,如下所示:
running+=new_dataifdelimiterinrunning:curr,running=running.split(delimiter)yieldcurr+delimiter
对于我的初始用例,单个文档的比1024个字符大得多,因此新数据永远不会包含多个分隔符。 但是对于较小的文档,一次读取可能会得到多个分隔符,然后解压缩.split()的结果将抛出ValueError。 因此,现在代码正确地检查并处理单个读取包含多个分隔符的情况。
现在它已经在一个模块中进行了编码和测试,我不必担心再次犯同样的错误。
许可证
麻省理工学院。