2013-02-16 2 views
1

У меня есть .tar.bz2 файлы с большим количеством небольших файлов json. Один архив может иметь около тысячи из них, а jsons небольшие (ниже 10 кБ, обычно ниже килобайта). В результате один архив после сжатия не превышает 100 КБ.Тарфика останавливается после первого обычного файла

Согласно 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 после прочтения второго элемента, хотя в архиве много файлов.

У меня есть ошибка где-то в этом коде?

+0

Вы уверены, что 'archive.next' возвращает None? Это также может быть неудачным на «tarinfo.isreg()», если это неверно, он может входить в спинлооп до вызова 'break'. – slezica

+0

Да, я добавил инструкцию 'print' для ее отладки. Кроме того, документировано, что 'archive.next' должен возвращать' None', но только когда он достигает конца архива ... также, 'isreg()' есть только для фильтрации каталогов, потому что нет смысла читать их содержание, насколько я знаю, это не имеет большого значения. – liori

+0

Я просто пробовал этот код локально, и я получаю те же результаты, что и вы. Это вопрос контракта, и вы вызвали мое любопытство :) Я посмотрю, что я могу узнать – slezica

ответ

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 
+0

В документации не указано, что объекты TarFile поддерживают протокол итератора ... – liori

+0

На странице документа приведены примеры этого использования, однако – slezica

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 
+0

default - выполнить автоопределение, и он работает, потому что он может прочитать первый элемент. – liori

+0

ОК - мой плохой У меня были подобные проблемы давным-давно, когда чтение дало одну запись. Возможно, это был случай программирования Cargo Cult с моей стороны. – sotapme

2

Обходной является использование 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 

Это яснее, и я буду держать пари, что это немного быстрее.

+0

И, по крайней мере, документирован. Спасибо. – liori

+0

@liori, я добавил длинное объяснение, почему это происходит, и некоторый альтернативный код, который вы должны использовать. –

+0

Вы правы, что в файлах 2.7 tar-файла явно не сказано, что «TarFile» поддерживает протокол итератора ». Тем не менее, «TarFile» имеет свойство «__iter__» и [второй пример кода] (http://docs.python.org/2.7/library/tarfile.html#examples) говорит 'для tarinfo в членах:', демонстрируя его использовать. –

Смежные вопросы