Jak pętli do EOF w Pythonie?

Muszę zapętlać, dopóki nie dotrę do końca obiektu podobnego do pliku, ale nie znajduję "oczywistego sposobu, aby to zrobić", co sprawia, że podejrzewam, że przeoczyłem coś, cóż, oczywistego. :-)

Mam stream (w tym przypadku jest to obiekt StringIO, ale ciekaw jestem również ogólnego przypadku), który przechowuje nieznaną liczbę rekordów w formacie " ", np.:

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

Teraz, jedynym jasnym sposobem, jaki mogę sobie wyobrazić, aby to odczytać, jest użycie (co myślę o) zainicjalizowanej pętli, co wydaje się trochę Nie-Pythoniczne:

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)

W języku podobnym do C, po prostu umieściłbym read(4) w klauzuli testowej while, ale oczywiście to nie zadziała w Pythonie. Jakieś pomysły na lepszy sposób, aby to osiągnąć?

Author: akashchandrakar, 2009-11-18

6 answers

Możesz połączyć iterację poprzez iter () {[3] } z Wartownikiem:

for block in iter(lambda: file_obj.read(4), ""):
  use(block)
 25
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 22:09:52

Czy widziałeś jak iterację nad wierszami w pliku tekstowym?

for line in file_obj:
  use(line)

Możesz zrobić to samo z własnym generatorem:

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)

Zobacz też:

 10
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 22:00:37

Wolę już wspomniane rozwiązanie oparte na iteratorze, aby przekształcić to w pętlę for. Innym rozwiązaniem pisanym bezpośrednio jest "pętla-i-pół" Knutha

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

Możesz zobaczyć przez porównanie, jak łatwo jest go wciągnąć do własnego generatora i użyć jako pętli for.

 5
Author: Andrew Dalke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 22:06:18

Widzę, zgodnie z przewidywaniami, że typowa i najpopularniejsza odpowiedź wykorzystuje bardzo wyspecjalizowane generatory do "odczytu 4 bajtów na raz". Czasami ogólność nie jest trudniejsza (i znacznie bardziej satysfakcjonująca; -), więc zaproponowałem następujące bardzo ogólne rozwiązanie: {]}

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

Teraz pożądany nagłówek pętli to po prostu: for len_name in funlooper(data.read, 4):.

Edit : made much more general by the wearedone idiom since a comment possigned my nieco mniej general previous version (hardcoding the exit test as if not data:) posiadania "ukrytej zależności", ze wszystkich rzeczy!-)

Zwykły szwajcarski nóż wojskowy pętli, itertools, też jest dobrze, oczywiście, jak zwykle:

import itertools as it

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

Lub, całkiem równoważnie:

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): ...
 3
Author: Alex Martelli,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 23:05:30

Znacznik EOF w Pythonie jest pustym ciągiem znaków, więc to, co masz, jest prawie najlepsze, co możesz uzyskać bez pisania funkcji do zawinięcia tego w iteratorze. Można by napisać w nieco bardziej pythoniczny sposób zmieniając while Jak:

while len_name:
    len_name = struct.unpack("<I", len_name)[0]
    names.append(data.read(len_name))
    len_name = data.read(4)
 1
Author: Tendayi Mawushe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-17 22:05:15

Wybrałbym funkcję Re sugestii Tendayi i iterator dla czytelności:

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)
 0
Author: John Keyes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-18 00:29:32