Cómo bucle hasta que EOF en Python?
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 "
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?
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)