1

我有.tar.bz2很多小文件的json文件。一个存档可能有大约数千个,并且 json 很小(低于 10kB,通常也低于 1kb)。因此,压缩后的单个存档不超过 100kB。

根据文档,以下函数应在 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 ,即使存档有很多文件。

我在这段代码的某个地方有错误吗?

4

3 回答 3

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()没有实现迭代器协议,因为它返回而不是Noneraise StopIteration

迭代器协议有两部分:容器元素上的“外部”部分返回迭代器,以及“内部”部分,即迭代器本身。

容器必须实现__iter__(),它返回一个对象,即迭代器。TarFile.__iter__()返回一个新TarIter对象。

迭代器本身 ( TarIter) 实现__iter__()(总是返回self)和next(). 它还必须对原始容器中的项目有自己的独立索引。这允许您在同一个容器上生成多个不同的迭代器,而不会使单独的迭代相互混淆。

TarFile.next()但是,它的迭代没有使用单独的索引,所以如果其他人使用由他们提供的伪迭代协议,TarFile他们会搞乱迭代。

这似乎就是这里发生的事情。在当前使用TarFile.extractfile(filename)中查找匹配文件,而不是您正在使用的文件。这会破坏“下一项”索引,导致在第一次调用后返回。TarFileTarFile.next()TarFile.__iter__()archive.next()Noneextractfile()

但是,如果您使用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

这更清楚,我敢打赌它也会快一点。

于 2013-02-17T19:06:01.757 回答
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
于 2013-02-16T18:27:19.677 回答
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
于 2013-02-16T18:42:49.543 回答