Comment boucle jusqu'à ce que EOF en Python?
Question
Je dois boucle jusqu'à ce que je frappe la fin d'un objet de type fichier, mais je ne suis pas la recherche d'une « façon évidente de le faire », ce qui me fait pense que je suis sur quelque chose, eh bien, évident. : -)
J'ai un flux (dans ce cas, il est un objet StringIO, mais je suis curieux de savoir le cas général, ainsi) qui stocke un nombre inconnu d'enregistrements dans «
data = StringIO("\x07\x00\x00\x00foobar\x00\x04\x00\x00\x00baz\x00")
Maintenant, la seule façon claire que je peux imaginer lire cela est d'utiliser (ce que je pense comme) une boucle initialisées, qui semble un peu non 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)
Dans un langage C comme, je venais de coller le read(4)
dans la clause de test du while
, mais bien sûr, cela ne fonctionnera pas pour Python. Toute réflexion sur une meilleure façon d'y arriver?
La solution
Vous pouvez combiner itération par iter () avec une sentinelle:
for block in iter(lambda: file_obj.read(4), ""):
use(block)
Autres conseils
Avez-vous vu comment itérer sur les lignes dans un fichier texte?
for line in file_obj:
use(line)
Vous pouvez faire la même chose avec votre propre générateur:
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)
Voir aussi:
Je préfère la solution iterator-déjà mentionné pour transformer cela en une boucle for. Une autre solution est écrite directement « boucle et demi » de Knuth
while 1:
len_name = data.read(4)
if not len_name:
break
names.append(data.read(len_name))
Vous pouvez voir par comparaison comment cela est facilement hissée dans son propre générateur et utilisé comme une boucle for.
Je vois, comme prévu, que la réponse typique et le plus populaire utilisent des générateurs très spécialisés pour « lire 4 octets à la fois ». Parfois, la généralité est pas plus difficile (et beaucoup plus gratifiant ;-), donc, je l'ai suggéré plutôt la solution suivante très générale:
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
Maintenant, votre tête de boucle souhaitée est juste. for len_name in funlooper(data.read, 4):
Modifier : fait beaucoup plus générale par l'idiome wearedone
depuis un commentaire accusé ma version précédente un peu moins générale (hardcoding le test de sortie comme if not data:
) d'avoir « une dépendance cachée », de toutes les choses! -)
Le couteau suisse habituelle en boucle, itertools
, est très bien aussi, Bien sûr, comme d'habitude:
import itertools as it
for len_name in it.takewhile(bool, it.imap(data.read, it.repeat(4))): ...
ou, tout à fait équivalente:
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): ...
Le marqueur EOF en python est une chaîne vide donc ce que vous avez est assez proche du mieux que vous allez obtenir sans écrire une fonction pour envelopper cela dans un itérateur. Je pourrais être écrit dans un peu de façon plus pythonique en changeant la while
comme:
while len_name:
len_name = struct.unpack("<I", len_name)[0]
names.append(data.read(len_name))
len_name = data.read(4)
Je vais avec la suggestion de Tendayi re fonction et iterator pour une meilleure lisibilité:
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)