[Python入門]バイナリファイルの操作:Python入門(3/3 ページ)
バイナリファイルからのデータの読み込み、structモジュールを利用したバイナリファイルへのデータの書き込みと読み込みの方法を紹介する。
バイナリファイルへの書き込み
次にバイナリファイルへの書き込みについて見ていこう。
書き込み用にバイナリファイルをオープンするには、open関数のパラメーターmodeに「'w'」や「'x'」と一緒に「'b'」を付加する。注意が必要なのは、テキストファイルではwriteメソッドなどに渡せたのは文字列だけだったのと同様、バイナリファイルではbytesオブジェクトだけということだ。
つまり、次のようなコードを書いてもエラーとなる。
myfile = open('myfile.bin', 'wb')
myfile.write(1) # 整数値「1」を書き込もうとする
これを実行すると、次のようにTypeError例外が発生する。
例外が発生したところで、一度、ファイルをクローズしておこう。
myfile.close()
structモジュール
上の例から分かるように、整数値にしろ、浮動小数点数値にしろ、文字列にしろ、全てのオブジェクトをbytesオブジェクトに変換してから書き込みを行う必要がある。整数(int型)にはto_bytesメソッドがあるので、これを使ってbytesオブジェクトに変換ができる。文字列はencodeメソッドでbytesオブジェクトに変換できる。これらだけなら、変換後のbytesオブジェクトをwriteメソッドで書き込んでやれば問題ない。
だが、浮動小数点数(float型)には同様なメソッドがない。そこで、これらを手軽にファイルに書き込むのに使えるstructモジュールを紹介しよう。
structモジュールにはpack、unpack、calcsizeなどの関数があり、これらを使って整数、浮動小数点数、文字列(をencodeメソッドでエンコードしたもの)をbytesオブジェクトに変換したり、逆にbytesオブジェクトからそれらを取り出したりできる。
bytesオブジェクトへ変換するには、「書式指定文字」と呼ばれる文字を組み合わせて、それらと実際に変換を行う値をpack関数に渡せばよい。このbytesオブジェクトに変換したものをバイナリファイルに書き込むことになる。
逆にバイナリファイルから読み出したデータは、自分でバイナリファイルに書き込んだデータなら、書き込み時に指定した書式指定文字がデータがどのような構成になっているかの仕様となるし、特定のフォーマットのファイルであれば、その仕様から(上のGIFファイルのように)データがどのような並びになっているかが分かるので、そこから書式指定文字が組み上げる。その書式指定文字とデータをunpack関数に渡すことで、バイナリファイルから読み出したbytesオブジェクトから、必要なデータを取り出せる。
書式指定文字には以下のようなものがある(一部。全てはPythonのドキュメント「struct --- バイト列をパックされたバイナリデータとして解釈する」を参照のこと)。
| 書式指定文字 | 説明 | サイズ(バイト数) |
|---|---|---|
| c | 文字 | 1 |
| b | 符号付き整数 | 1 |
| B | 符号なし整数 | 1 |
| h | 符号付き整数 | 2 |
| H | 符号なし整数 | 2 |
| i | 符号付き整数 | 4 |
| I | 符号なし整数 | 4 |
| l | 符号付き整数 | 4 |
| L | 符号なし整数 | 4 |
| f | 浮動小数点数 | 4 |
| d | 浮動小数点数 | 8 |
| s | 文字列 | - |
| < | リトルエンディアンの指定 | - |
| > | ビッグエンディアンの指定 | - |
| structで使用する書式指定文字 | ||
例えば、整数を1バイトの符号付き整数としてbytesオブジェクトに変換するのなら、書式指定文字には「'b'」を指定して、変換したい値と一緒にpack関数に渡す。以下に例を示す。
from struct import pack, unpack, calcsize
data = pack('b', 100)
print(data)
print(f"100 == b'{chr(100)}'")
これを実行すると、次のようになる。
文字「'd'」のASCII値は10進数表記の「100」なので、100をpack関数に渡してbytesオブジェクトに変換すると、「b'd'」となることには注意しよう。逆に得られたbytesオブジェクトを整数に変換するには次のようにする。
result = unpack('b', data)
print(result)
これを実行すると、次のようになる。
結果を見ると分かる通り、得られたデータはタプルに格納されることに注意しよう。ここでは書式指定文字を1文字だけしか使っていなかったので、得られた値も1つだけだが、複数の書式指定文字を指定すれば、それらの数だけデータが得られる。
浮動小数点数についても試してみよう。
data = pack('f', 1.1)
print(data)
これを実行すると次のようになる。
このようにして、pack関数やunpack関数を使うことで、整数や浮動小数点数、文字列などを簡単にbytesオブジェクトに変換できる。
structモジュールを使ってバイナリファイルを読み書きする
では、実際にstructモジュールを使ってバイナリファイルにデータを書き込んで、それを読み込み直してみよう。
まず、ここではタプルを要素とするリストを作っておく。
mydata = [(1, 'FOO', 1023), (2, 'BAR', 80), (3, 'BAZ', 4000)]
要素となっているタプルには、ID、名前、データが収められているとする。このとき、名前の文字数が同じになっていることにも注意しよう。structモジュールでは(文字数を含めて)このような定型フォーマットのデータを簡単にbytes列に変換できる。ここでは、これらを次のような構成でbytesオブジェクトに置き換えることにする。
- ID:4バイトの整数
- 名前:3文字の文字列
- データ:4バイトの整数
これらを書式指定文字で表現すると「l3sll」となる。「s」の前にある「3」は、それが3文字で構成される文字列であることを意味する。
例えば、リストの最初の要素をbytesオブジェクトに変換するのであれば、以下のようになる(文字列が含まれているため、encodeメソッドでbytes型に一度変換しておく必要があり、スッキリとは書けてはいない)。
result = pack('l3sl', mydata[0][0], mydata[0][1].encode(), mydata[0][2])
print(result)
これにより、3つのデータが1つのbytesオブジェクトにパックされる。
要するにこんな感じで、リストの各要素(タプル)をbytesオブジェクトにパックして、それをwriteメソッドでバイナリファイルに書き込んでやればいいということだ。実際のコードは次のようになる。
myfile = open('mydata.data', 'wb')
for item in mydata:
result = pack('l3sl', item[0], item[1].encode(), item[2])
myfile.write(result)
myfile.close()
今度は書き込んだデータを読み込んで、元のデータに復元できるかを確認してみよう。このときには、タプル1つ当たりのデータサイズが必要になる。これを確認するのに使えるのが、calcsize関数だ。これに書式指定文字を渡せば、そのデータのサイズが分かる。
size = calcsize('l3sl')
myfile = open('mydata.data', 'rb')
content = myfile.read(size)
while content:
restored_data = unpack('l3sl', content)
print(restored_data)
content = myfile.read(size)
myfile.close()
ここでは、calcsize関数で調べたデータサイズごとにreadメソッドでファイルから内容を読み込んで、それをunpack関数に渡して、データを取り出して、画面に表示している。それが終わったら、もう一度データサイズだけファイルから読み込みを行い、ファイルが空になるまでそれを続けるようにした。
実行すると、次のようになる。
画像を見ると分かるように、3つのデータを含んだタプルが順次取り出されていることが分かる。また、名前要素はbytesオブジェクトになっているので、実際にそれを利用するのであれば、文字列でデコードする必要があることには注意しよう。
structモジュールを使って、GIFファイルから読み込みを行うコード
最後に、先ほどのGIFファイルから仕様とサイズを得る関数を、structモジュールを使って書き直してみよう。先ほどの話だと、必要なデータは次のようになる。
- GIFシグネチャは「GIF」の後に「87a」か「89a」が続く
- その後は、2バイトで画面の横幅と縦幅がある
これを書式指定文字にすると「6shh」となる。これが分かれば、関数はすぐに書ける。実際のコードは次のようになる。
def get_dimension_with_struct(filename):
myfile = open(filename, 'rb')
content = myfile.read(10)
myfile.close()
(spec, width, height) = unpack('6shh', content)
print(f'this file is {spec.decode()}, size: {width} x {height}')
ここでは先頭の10バイトを最初に読み込んでしまって、それを上述の書式指定文字と一緒にunpack関数に渡している(バイトオーダーを指定していないので、環境によっては間違った値に変換されるかもしれない)。
unpack関数はbytesオブジェクト化されている「GIF+仕様」と、横幅、縦幅をタプルにまとめて返すので、これを3つの変数spec、width、heightに代入するようにしている。後は、それらを画面に表示するだけだ。
実際に実行した結果を以下に示す。
こちらのコードでは、int.from_bytesメソッド呼び出しを書かずとも自動的にbytesオブジェクトから整数値を取り出せるなど、元のコードと比べて、若干簡単にはなっているはずだ。このように、自分がバイナリファイルに書き込んだデータでなくとも、仕様を基に自分で書式指定文字を組み立てることで、外部ファイルを読み込んで、そのパースを行うのにもstructモジュールは使える。
structモジュールを使うと、このように比較的簡単に定型データをbytesオブジェクトに変換して、それをバイナリファイルに書き込んだり、バイナリファイルから読み込んだりできる。しかし、特定のフォーマットを持つデータを読み書きするには、pickleモジュールなどより便利なモジュールも用意されている。これらについては次回紹介しよう。
まとめ
今回はバイナリファイルからのデータの読み込みと、structモジュールを使ったバイナリファイルへのデータの書き込み(と読み込み)について見た。次回はpickleモジュールなど、「オブジェクトのシリアライズ(直列化)」を行うモジュールについて見ていこう。
今回のまとめ
- バイナリファイルをオープンするにはopen関数のパラメーターmodeに「'b'」を付加する
- バイナリファイルとのやりとりにはbytesオブジェクトを使用する
- bytesオブジェクトは0〜255の範囲の値を要素とするシーケンス
- 文字列とbytesオブジェクトとの変換には文字列のencodeメソッドと、bytes型のdecodeメソッドを使える
- バイナリファイルから読み込んだデータは、何らかの手段でもともとのデータ型に変換する必要がある
- これを手軽に行うのにstructモジュールを使用できる
Copyright© Digital Advantage Corp. All Rights Reserved.



