すなぶろ

Pythonistaを目指しているつもりが、Hyのおかげで無事脱線。Clojurian目指すことになりました。

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に渡す<は「リトルエンディアン」の明示、Iunsigned 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がなくなりました。代わりにiterlambdaが登場しています。ネストも行数も少なくなって、なんかPythonっぽいコードになりました。forenumerateがうまく使えると、気分だけはPython中級者になった気がします。

ここで登場しているのは「第二引数を取るiter()関数」です。iter()は名前の通りイテレータを生成する関数ですが、第二引数が提供されている場合動作が大きく変わります。まず第一引数の呼び出し可能オブジェクトlambda: f.read(4)を呼び出し、この結果が第二引数b''であればStopIteration例外を投げます。先例のif bytesと同じことがここで行われています。b''でなかった場合、そのまま値を返します。

自分用のメモでした。