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

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

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

data = StringIO("x07x00x00x00foobarx00x04x00x00x00bazx00")

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

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 ответов


вы можете комбинировать итерации через iter () С дозорного:

for block in iter(lambda: file_obj.read(4), ""):
  use(block)

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

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)

Читайте также:

  • .читать

Я предпочитаю уже упомянутое решение на основе итератора, чтобы превратить это в цикл for. Другое решение, написанное непосредственно,-это "петля с половиной" кнута

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

вы можете видеть по сравнению, как это легко поднимается в свой собственный генератор и используется в качестве for-loop.


Я вижу, как и предсказывалось, что типичный и самый популярный ответ использует очень специализированные генераторы для"чтения 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): ...

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

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

Я бы пошел с 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)