Как зациклить до EOF в Python?
Мне нужно зацикливаться, пока я не достигну конца файла-подобного объекта, но я не нахожу "очевидного способа сделать это", что заставляет меня подозревать, что я что-то упускаю, ну, очевидно:-)
У меня есть поток (в данном случае это объект 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. Есть мысли о лучшем способе сделать это?
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.
Как и предполагалось, я вижу, что типичный и самый популярный ответ - использование очень специализированных генераторов для "чтения 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)
Я бы пошел с предложением 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)