Pythonでバイナリファイルをnバイトずつ取り出したかった
- やりたかったこと
- 4バイトずつ取り出す
- リトルエンディアンで
unsigned int
に変換する - リストにぶちこむ
read()
, struct.unpack()
などでできそうな気はしたんですが、Pythonっぽいやり方ないのかなと探した結果をメモしておきます。オーバーヘッドなどを考えると最適解ではないと思うので、ビッグデータには向いていないはずです。
whileとifを使う
わかりやすいけど冗長なコード。
import struct data = [] with open('binary.dat', 'rb') as f: while True: bytes = f.read(4) if bytes: data.append(struct.unpack('<I', bytes)[0]) else: break
ストリームf
に対してread(4)
を呼び出し、bytes
に4バイトずつ格納していくwhile
ループです。ファイルの終端に到達した場合はbytes
の中身がb''
となり、break
でループから抜け出します(bool(b'') == False
であるため)。
struct.unpack
に渡す<
は「リトルエンディアン」の明示、I
はunsigned int
の明示です。struct.unpack
は値がひとつでもタプルで返すため、[0]
で値を取り出しています。
別にこのイディオムが悪いと思うわけではありませんが、while
でネストがひとつ深くなるので、他の方法があればと探して見つけたのが以下。
イテレータを生成する
import struct data = [] with open('binary.dat', 'rb') as f: for bytes in iter(lambda: f.read(4), b''): data.append(struct.unpack('<I', bytes)[0])
while
ではなくfor
になり、空チェックのif
がなくなりました。代わりにiter
とlambda
が登場しています。ネストも行数も少なくなって、なんかPythonっぽいコードになりました。for
やenumerate
がうまく使えると、気分だけはPython中級者になった気がします。
ここで登場しているのは「第二引数を取るiter()関数」です。iter()
は名前の通りイテレータを生成する関数ですが、第二引数が提供されている場合動作が大きく変わります。まず第一引数の呼び出し可能オブジェクトlambda: f.read(4)
を呼び出し、この結果が第二引数b''
であればStopIteration
例外を投げます。先例のif bytes
と同じことがここで行われています。b''
でなかった場合、そのまま値を返します。
自分用のメモでした。