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 « » format, par exemple:

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?

Était-ce utile?

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)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top