1

小さなファイルが.tar.bz2たくさんあるファイルがありjsonます。1 つのアーカイブには約数千のファイルが含まれている可能性があり、json は小さい (10kB 未満、通常は 1 キロバイト未満)。その結果、圧縮後の単一のアーカイブは 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()アーカイブに多くのファイルがあるにもかかわらず、2 番目のメンバーを読み取った後に 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

なぜこれが起こっているのかについては、 raise ではなく返すため、 iterator プロトコルを実装してTarFile.next()いませNoneStopIteration

イテレータ プロトコルには 2 つの部分があります。イテレータを返すコンテナ要素の「外側」部分と、イテレータ自体である「内側」部分です。

コンテナは、イテレータである新しい__iter__()オブジェクトを返す を実装する必要があります。新しいオブジェクトを返します。TarFile.__iter__()TarIter

イテレータ自体 ( ) は(常に を返す) およびをTarIter実装します。また、元のコンテナー内のアイテムへの独自の独立したインデックスも必要です。これにより、別々の反復が互いに混乱することなく、同じコンテナーに対して複数の異なる反復子を生成できます。__iter__()selfnext()

TarFile.next()、しかし、その反復に別のインデックスを使用しないため、誰かが提供する疑似反復プロトコルを使用するTarFileと、反復が台無しになります。

これがここで起こっていることのようです。あなたが使用していたものではなく、現在の使用TarFile.extractfile(filename)で一致するファイルを探します。これにより、「次のアイテム」インデックスが破損し、最初の呼び出しの後に返されます。TarFileTarFile.next()TarFile.__iter__()archive.next()Noneextractfile()

ただし、 を使用するextractfile(tarinfo)と、オブジェクト内で一致するファイル名を探すことなく、文字列の内容を抽出するのにtarinfo十分なメタデータがオブジェクトに含まれます。したがって、はおそらく よりも高速です。TarFilearchivearchive.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

@upside コードの方が理にかなっていますが、元の OP コードを次のように変更すると、興味がわきます。

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 に答える