10

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

"<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's test 句に を挿入するだけですが、もちろん Python では機能しません。これを達成するためのより良い方法について何か考えはありますか?

4

6 に答える 6

27

iter()による反復をセンチネルと組み合わせることができます。

for block in iter(lambda: file_obj.read(4), ""):
  use(block)
于 2009-11-17T22:09:52.213 に答える
10

テキスト ファイル内の行を反復処理する方法を見たことがありますか?

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)

以下も参照してください。

于 2009-11-17T22:00:37.657 に答える
5

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

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

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

于 2009-11-17T22:06:18.177 に答える
3

予測どおり、典型的で最も一般的な答えは、非常に特殊なジェネレーターを使用して「一度に 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):

編集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): ...
于 2009-11-17T22:02:49.767 に答える
1

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

while len_name:
    len_name = struct.unpack("<I", len_name)[0]
    names.append(data.read(len_name))
    len_name = data.read(4)
于 2009-11-17T22:05:15.637 に答える
0

読みやすさのために、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)
于 2009-11-17T22:20:44.187 に答える