Tarfile在第一个常规文件后停止

时间:2013-02-16 18:14:54

标签: python tar

我有.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,即使存档包含大量文件。

我在此代码中的某处有错误吗?

3 个答案:

答案 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