Pergunta

Eu preciso fazer um loop até chegar ao fim de um objeto semelhante a um arquivo, mas não estou encontrando uma "maneira óbvia de fazê-lo", o que me faz suspeitar que estou negligenciando algo, bem, óbvio. :-)

Eu tenho um fluxo (neste caso, é um objeto Stringio, mas também estou curioso sobre o caso geral) que armazena um número desconhecido de registros em "u003Clength>u003Cdata> "Formato, por exemplo:

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

Agora, a única maneira clara de imaginar ler isso está usando (o que eu acho como) um loop inicializado, que parece um pouco pitônico:

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)

Em uma linguagem do tipo C, eu apenas enfia o read(4) no whileCláusula de teste, mas é claro que isso não funcionará para o Python. Alguma opinião sobre uma maneira melhor de conseguir isso?

Foi útil?

Solução

Você pode combinar iteração através iter () com um sentinela:

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

Outras dicas

Você viu como iterar sobre linhas em um arquivo de texto?

for line in file_obj:
  use(line)

Você pode fazer a mesma coisa com seu próprio gerador:

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)

Veja também:

Prefiro a solução já mencionada baseada em iterador para transformar isso em um loop for. Outra solução escrita diretamente é o "loop e meio" de Knuth

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

Você pode ver, em comparação, como isso é facilmente içado em seu próprio gerador e usado como um loop for.

Vejo, como previsto, que a resposta típica e mais popular está usando geradores muito especializados para "ler 4 bytes de cada vez". Às vezes, a generalidade não é mais difícil (e muito mais gratificante ;-), então, sugeri a seguinte solução muito geral:

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

Agora o cabeçalho do loop desejado é apenas: for len_name in funlooper(data.read, 4):.

Editar: tornado muito mais geral pelo wearedone idioma desde que um comentário acusou minha versão anterior um pouco menos geral (codificando o teste de saída como if not data:) de ter "uma dependência oculta", de todas as coisas!-)

A usual faca do exército suíço de loop, itertools, está bem também, é claro, como sempre:

import itertools as it

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

ou, de maneira bastante equivalente:

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): ...

O marcador EOF no Python é uma corda vazia, então o que você tem é bem próximo do melhor que você obterá sem escrever uma função para envolver isso em um iterador. Eu poderia ser escrito de maneira um pouco mais pitônica, mudando o while Curti:

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

Eu iria com a sugestão de tendayi e a função e o iterador para legibilidade:

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)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top