我有.tar.bz2
个文件,包含许多小json
个文件。单个存档可能有大约数千个,并且jsons很小(低于10kB,通常也低于一千字节)。因此,压缩后的单个存档不会超过100kB。
根据the documentation,以下函数应该返回tar文件中所有常规文件的迭代器,返回它们的tarinfo结构和数据。
import tarfile
def tariter(filename):
with tarfile.open(filename) as archive:
while True:
tarinfo = archive.next()
if tarinfo is None:
break
if tarinfo.isreg():
handle = archive.extractfile(tarinfo.name)
data = handle.read()
handle.close()
yield tarinfo, data
然而,它只是返回一个迭代器,它返回它的第一个文件(连同内容),然后停止。显然,archive.next()
在读取第二个成员后返回None,即使存档包含大量文件。
我在此代码中的某处有错误吗?
答案 0 :(得分:3)
解决方法是直接使用extractfile
和tarinfo而不是名称。这有效:
def tariter(filename):
with tarfile.open(filename) as archive:
while True:
tarinfo = archive.next()
if tarinfo is None:
break
if tarinfo.isreg():
handle = archive.extractfile(tarinfo) # LINE CHANGED
data = handle.read()
handle.close()
yield tarinfo, data
至于为什么发生这种情况:TarFile.next()
不实现迭代器协议,因为它返回None
而不是StopIteration
}。
迭代器协议有两个部分:容器元素上的“外部”部分返回迭代器,“内部”部分是迭代器本身。
容器必须实现__iter__()
,它返回一个 new 对象,它是迭代器。 TarFile.__iter__()
会返回一个新的TarIter
对象。
迭代器本身(TarIter
)实现__iter__()
(总是返回self
)和next()
。它还必须有自己的原始容器中项目的独立索引。这允许您在同一个容器上生成多个不同的迭代器,而不会相互混淆。
TarFile.next()
不为其迭代使用单独的索引,因此如果其他人使用TarFile
提供的伪迭代协议,他们将搞乱迭代。
这似乎就是这里发生的事情。 TarFile.extractfile(filename)
使用TarFile
代替TarFile.next()
来查找当前TarFile.__iter__()
中的匹配文件,而您使用的是archive.next()
。这会破坏“下一项”索引,导致None
在第一次extractfile()
来电后返回extractfile(tarinfo)
。
但是,如果您使用tarinfo
,则TarFile
对象中有足够的元数据可供archive
提取字符串内容,而无需通过archive.extractfile(tarinfo)
对象查找匹配文件名。因此,archive.extractfile(tarinfo.name)
可能比TarFile
更快。
通常,集合对象(如TarFile.next()
)应该不迭代自己,但会生成一个新对象来迭代它们。仅存在def tariter(filename):
with tarfile.open(filename) as archive:
# use TarIter object for iteration over archive
for tarinfo in archive:
if tarinfo.isreg():
handle = archive.extractfile(tarinfo)
data = handle.read()
handle.close()
yield tarinfo, data
糟糕设计的气味。也许有充分的理由,但你不必使用它!
请改为:
{{1}}
这更清楚,我敢打赌它也快一点。
答案 1 :(得分:1)
我不知道为什么next()
失败了(我本地也失败了),但是这样做(看起来更干净):
import tarfile
def tariter(filename):
with tarfile.open(filename) as archive:
for tarinfo in archive:
if tarinfo.isreg():
handle = archive.extractfile(tarinfo.name)
data = handle.read()
handle.close()
yield tarinfo, data
答案 2 :(得分:0)
只是出于利益的考虑,将原始的OP代码更改为以下作品,尽管@upside代码更有意义。
import tarfile
def tariter(filename):
with tarfile.open(filename) as archive:
it = archive.__iter__() # CHANGE
while True:
tarinfo = it.next() # CHANGE
if tarinfo is None:
break
if tarinfo.isreg():
handle = archive.extractfile(tarinfo.name)
data = handle.read()
handle.close()
yield tarinfo, data