web-dev-qa-db-ja.com

PythonでEOFまでループする方法は?

ファイルのようなオブジェクトの終わりに到達するまでループする必要がありますが、「それを行うための明白な方法」が見つからないため、何かを見落としているのではないかと思われます。 :-)

不明な数のレコードを「<length> <data>」形式で格納するストリーム(この場合はStringIOオブジェクトですが、一般的なケースについても興味があります)があります。例:

_data = StringIO("\x07\x00\x00\x00foobar\x00\x04\x00\x00\x00baz\x00")
_

さて、これを読むために私が想像できる唯一の明確な方法は、初期化されたループを使用することです(私が考えるもの)。これは少し非Python的です:

_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)
_

Cのような言語では、read(4)whileのテスト句に貼り付けるだけですが、もちろんPythonでは機能しません。これを達成するためのより良い方法について何か考えはありますか?

10
Ben Blank

iter() を介して反復をセンチネルと組み合わせることができます:

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

テキストファイルの行を繰り返す方法を見たことがありますか?

for line in file_obj:
  use(line)

独自のジェネレーターでも同じことができます。

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)

参照:

10
Roger Pate

これをforループに変えるには、すでに述べたイテレータベースのソリューションを好みます。直接書かれた別の解決策は、Knuthの「ループアンドハーフ」です。

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

比較すると、それがどのように独自のジェネレーターに簡単に引き上げられ、forループとして使用されるかがわかります。

5
Andrew Dalke

予想通り、典型的で最も人気のある答えは、非常に特殊なジェネレーターを使用して「一度に4バイトを読み取る」ことです。一般性がそれほど難しくない(そしてはるかにやりがいがある;-)場合もあるので、代わりに次の非常に一般的な解決策を提案しました。

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

これで、目的のループヘッダーはfor len_name in funlooper(data.read, 4):になります。

Edit:コメントが私の少し一般的ではない以前のバージョンを非難したのでwearedoneイディオムによってはるかに一般的になりました(終了テストをif not data:としてハードコーディングします) )すべてのものの「隠された依存関係」を持っていることの!-)

ループの通常のスイスアーミーナイフ itertools も、もちろん、いつものように問題ありません。

import itertools as it

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

または、まったく同じように:

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
Alex Martelli

EOFマーカーin pythonは空の文字列であるため、これをまとめる関数を記述せずに取得できるものにかなり近いものになります。イテレータで。whileを次のように変更することで、もう少しPythonのように書くことができます。

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

読みやすさのために、Tendayiの提案re関数とイテレータを使用します。

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
John Keyes