回避策は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()
いません。None
StopIteration
イテレータ プロトコルには 2 つの部分があります。イテレータを返すコンテナ要素の「外側」部分と、イテレータ自体である「内側」部分です。
コンテナは、イテレータである新しい__iter__()
オブジェクトを返す を実装する必要があります。新しいオブジェクトを返します。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
これはより明確であり、少し高速でもあるに違いありません。