Pregunta

Necesito bucle hasta que llegué a la final de un objeto de tipo fichero, pero no voy a encontrar una "forma obvia de hacerlo", lo que me hace sospechar que estoy pasando por alto algo, bueno, obvio. : -)

Tengo una corriente (en este caso, se trata de un objeto StringIO, pero tengo curiosidad sobre el caso general, así) que almacena un número desconocido de registros en " " formato, por ejemplo:

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

Ahora, la única manera clara que puedo imaginar para leer esto está utilizando (lo que pienso de como) un bucle inicializado, que parece un poco un-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)

En un lenguaje similar a C, que acababa de pegar la read(4) en la cláusula de prueba del while, pero por supuesto que no va a funcionar para Python. Alguna idea de una mejor manera de lograr esto?

¿Fue útil?

Solución

Puede combinar iteración a través iter () con un centinela:

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

Otros consejos

¿Ha visto cómo iterar a través de líneas en un archivo de texto?

for line in file_obj:
  use(line)

Puede hacer lo mismo con su propio generador:

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)

Vea también:

Yo prefiero la solución basada en la ya mencionada iterador para convertir esto en un bucle para. Otra solución escrita directamente es "loop-y-a-media" de Knuth

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

Se puede ver mediante la comparación de cómo eso es izada fácilmente en su propio generador y se utiliza como un bucle de.

Veo, como se predijo, que la respuesta típica y más populares están utilizando generadores muy especializadas para "leer 4 bytes a la vez". A veces generalidad no es ningún lugar la siguiente solución muy general más difícil (y mucho más gratificante ;-), así, he sugerido:

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

Ahora su cabecera bucle deseada es simplemente:. for len_name in funlooper(data.read, 4):

Editar : hecho mucho más general por el lenguaje wearedone desde un comentario acusó a mi anterior versión ligeramente menos general (codificando la prueba de salida como if not data:) de tener "una dependencia oculto", de todas las cosas! -)

La navaja suiza usual de bucle, itertools , está muy bien también, de Por supuesto, como de costumbre:

import itertools as it

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

o, equivalentemente bastante:

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

El marcador EOF en Python es una cadena vacía así que lo que tenemos es bastante cerca de lo mejor que se van a poner sin necesidad de escribir una función para terminar con esto en un iterador. Podría ser escrito de una manera poco más Pythonic cambiando el while como:

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

Me quedo con la sugerencia de Tendayi re función y iterador para facilitar la lectura:

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 bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top