Обходной является использование 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__()
, который возвращает объект , который является итератором. 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
Это яснее, и я буду держать пари, что это немного быстрее.
Вы уверены, что 'archive.next' возвращает None? Это также может быть неудачным на «tarinfo.isreg()», если это неверно, он может входить в спинлооп до вызова 'break'. – slezica
Да, я добавил инструкцию 'print' для ее отладки. Кроме того, документировано, что 'archive.next' должен возвращать' None', но только когда он достигает конца архива ... также, 'isreg()' есть только для фильтрации каталогов, потому что нет смысла читать их содержание, насколько я знаю, это не имеет большого значения. – liori
Я просто пробовал этот код локально, и я получаю те же результаты, что и вы. Это вопрос контракта, и вы вызвали мое любопытство :) Я посмотрю, что я могу узнать – slezica