Domanda

Ho bisogno di ciclo fino a quando mi ha colpito alla fine di un oggetto simile a file, ma io non sto trovando un "modo ovvio per farlo", il che mi fa sospettare che sto affaccia qualcosa, beh, ovvio. : -)

Ho un flusso (in questo caso, si tratta di un oggetto StringIO, ma io sono curioso di sapere il caso generale come bene) che memorizza un numero imprecisato di record in " " formato, ad esempio:

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

Ora, l'unico modo chiaro posso immaginare di leggere questo sta usando (quello che penso come) un ciclo inizializzato, che sembra un po 'scomodi 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)

In un linguaggio simile al C, avevo appena bastone il read(4) nella clausola di prova del while, ma, naturalmente, che non funzionerà per Python. Ogni pensiero su un modo migliore per ottenere questo risultato?

È stato utile?

Soluzione

È possibile combinare iterazione iter () con una sentinella:

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

Altri suggerimenti

Hai visto come iterare su linee in un file di testo?

for line in file_obj:
  use(line)

Si può fare la stessa cosa con il proprio generatore:

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)

Vedi anche:

Io preferisco la soluzione iteratore basata già accennato a trasformare questo in un ciclo for. Un'altra soluzione scritta direttamente è "loop-e-un-metà" di Knuth

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

Si può vedere dal confronto come sia facilmente issato nel proprio generatore e usato come un ciclo for.

vedo, come previsto, che la risposta tipica e più popolare sta utilizzando generatori molto specializzati di "leggere 4 byte alla volta". A volte generalità non è un qualsiasi posto la seguente soluzione molto generale più difficile (e molto più gratificante ;-), quindi, che ho suggerito:

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

Ora l'intestazione loop desiderato è solo:. for len_name in funlooper(data.read, 4):

Modifica : reso molto più generale per l'idioma wearedone dal momento che un commento accusato la mia versione leggermente meno generale precedente (hardcoding il test di uscita come if not data:) di avere "una dipendenza nascosta", di tutte le cose! -)

Il solito coltellino svizzero di looping, itertools , è troppo bella, di Naturalmente, come al solito:

import itertools as it

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

o, del tutto 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): ...

L'indicatore EOF in python è una stringa vuota in modo da quello che hai è abbastanza vicino alla migliore che si sta per ottenere senza scrivere una funzione per avvolgere questo in un iteratore. Potrei essere scritto in un po 'di più divinatorio cambiando il while come:

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

mi piacerebbe andare con il suggerimento di Tendayi re la funzione e iteratore per migliorare la leggibilità:

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)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top