Вопрос

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

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

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

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

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.Есть какие-нибудь мысли о том, как лучше это сделать?

Это было полезно?

Решение

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

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.

Как и предполагалось, я вижу, что типичным и наиболее популярным ответом является использование очень специализированных генераторов для «чтения 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 представляет собой пустую строку, поэтому то, что у вас есть, довольно близко к лучшему, что вы можете получить без написания функции, которая обернет это в итератор.Меня можно было бы написать немного более питоническим способом, изменив while нравиться:

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

Я бы использовал функцию re и итератор предложения 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)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top