ファイルのようなオブジェクトの終わりに到達するまでループする必要がありますが、「それを行うための明白な方法」が見つからないため、何かを見落としているのではないかと思われます。 :-)
不明な数のレコードを「<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では機能しません。これを達成するためのより良い方法について何か考えはありますか?
iter() を介して反復をセンチネルと組み合わせることができます:
for block in iter(lambda: file_obj.read(4), ""):
use(block)
テキストファイルの行を繰り返す方法を見たことがありますか?
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)
参照:
これをforループに変えるには、すでに述べたイテレータベースのソリューションを好みます。直接書かれた別の解決策は、Knuthの「ループアンドハーフ」です。
while 1:
len_name = data.read(4)
if not len_name:
break
names.append(data.read(len_name))
比較すると、それがどのように独自のジェネレーターに簡単に引き上げられ、forループとして使用されるかがわかります。
予想通り、典型的で最も人気のある答えは、非常に特殊なジェネレーターを使用して「一度に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): ...
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)
読みやすさのために、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)