Как цикл до EOF в Python? - PullRequest
       52

Как цикл до EOF в Python?

10 голосов
/ 18 ноября 2009

Мне нужно зацикливаться, пока я не достигну конца файла-подобного объекта, но я не нахожу «очевидного способа сделать это», что заставляет меня подозревать, что я что-то упускаю, ну, очевидно. : -)

У меня есть поток (в данном случае это объект StringIO, но мне также интересен общий случай), который хранит неизвестное количество записей в формате "", например:

data = StringIO("\x07\x00\x00\x00foobar\x00\x04\x00\x00\x00baz\x00")

Теперь, единственный ясный способ, которым я могу себе представить, чтобы прочитать это, - это использовать (то, что я думаю как) инициализированный цикл, который кажется немного непифоническим:

len_name = data.read(4)

while len_name != "":
    len_name = struct.unpack("<I", len_name)[0]
    names.append(data.read(len_name))

    len_name = data.read(4)

На языке, подобном C, я бы просто вставил read(4) в условие теста while, но, конечно, это не будет работать для Python. Есть мысли о лучшем способе сделать это?

Ответы [ 6 ]

26 голосов
/ 18 ноября 2009

Вы можете объединить итерацию через iter () с часовым:

for block in iter(lambda: file_obj.read(4), ""):
  use(block)
10 голосов
/ 18 ноября 2009

Вы видели, как перебирать строки в текстовом файле?

for line in file_obj:
  use(line)

Вы можете сделать то же самое с вашим собственным генератором:

def read_blocks(file_obj, size):
  while True:
    data = file_obj.read(size)
    if not data:
      break
    yield data

for block in read_blocks(file_obj, 4):
  use(block)

Смотри также:

5 голосов
/ 18 ноября 2009

Я предпочитаю уже упомянутое решение на основе итераторов, чтобы превратить это в цикл for. Еще одно решение, написанное напрямую, - «петлевое» Кнута

while 1:
    len_name = data.read(4)
    if not len_name:
        break
    names.append(data.read(len_name))

Из сравнения видно, как его легко подключить к собственному генератору и использовать в качестве цикла for.

3 голосов
/ 18 ноября 2009

Я вижу, как и предсказывалось, что типичный и самый популярный ответ использует очень специализированные генераторы для «чтения 4 байтов за раз». Иногда общность не сложнее (и гораздо полезнее ;-), поэтому я предложил вместо этого следующее очень общее решение:

import operator
def funlooper(afun, *a, **k):
  wearedone = k.pop('wearedone', operator.not_)
  while True:
    data = afun(*a, **k)
    if wearedone(data): break
    yield data

Теперь желаемый заголовок цикла: for len_name in funlooper(data.read, 4):.

Редактировать : сделано гораздо более общим с помощью идиомы wearedone, поскольку комментарий обвинил мою чуть менее общую предыдущую версию (жестко закодировать выходной тест как if not data:) в "скрытой зависимости", все! -)

Обычный швейцарский армейский нож зацикливания, itertools, тоже подойдет, конечно, как обычно:

import itertools as it

for len_name in it.takewhile(bool, it.imap(data.read, it.repeat(4))): ...

или, что вполне эквивалентно:

import itertools as it

def loop(pred, fun, *args):
  return it.takewhile(pred, it.starmap(fun, it.repeat(args)))

for len_name in loop(bool, data.read, 4): ...
1 голос
/ 18 ноября 2009

Маркер EOF в python - это пустая строка, поэтому то, что у вас есть, довольно близко к лучшему, что вы получите, не написав функцию, которая обернет это в итераторе. Я мог бы написать чуть более питонически, изменив while как:

while len_name:
    len_name = struct.unpack("<I", len_name)[0]
    names.append(data.read(len_name))
    len_name = data.read(4)
0 голосов
/ 18 ноября 2009

Я бы пошел с предложением Tendayi и функцией итератора для удобства чтения:

def read4():
    len_name = data.read(4)
    if len_name:
        len_name = struct.unpack("<I", len_name)[0]
        return data.read(len_name)
    else:
        raise StopIteration

for d in iter(read4, ''):
    names.append(d)
...